From 3789109f3e737756ffc8d31791b8326b16919733 Mon Sep 17 00:00:00 2001 From: F4ever Date: Tue, 16 Apr 2024 21:18:38 +0200 Subject: [PATCH 01/21] contracts create --- src/modules/accounting/accounting.py | 53 ++---- src/modules/accounting/extra_data.py | 2 +- src/modules/accounting/typings.py | 19 +- src/modules/ejector/ejector.py | 50 +---- src/modules/submodules/consensus.py | 72 +++---- src/modules/submodules/typings.py | 3 + src/providers/execution/__init__.py | 0 src/providers/execution/base_interface.py | 22 +++ src/providers/execution/contracts/__init__.py | 0 .../execution/contracts/accounting_oracle.py | 46 +++++ .../execution/contracts/base_oracle.py | 115 +++++++++++ src/providers/execution/contracts/burner.py | 30 +++ .../execution/contracts/exit_bus_oracle.py | 67 +++++++ .../execution/contracts/hash_consensus.py | 132 +++++++++++++ src/providers/execution/contracts/lido.py | 101 ++++++++++ .../execution/contracts/lido_locator.py | 124 ++++++++++++ .../contracts/oracle_daemon_config.py | 121 ++++++++++++ .../contracts/oracle_report_sanity_checker.py | 30 +++ .../execution/contracts/staking_router.py | 43 +++++ .../contracts/withdrawal_queue_nft.py | 150 +++++++++++++++ src/services/bunker.py | 21 +- src/services/exit_order_iterator.py | 13 +- src/services/exit_order_iterator_state.py | 7 +- src/services/prediction.py | 10 +- src/services/safe_border.py | 57 ++---- src/services/validator_state.py | 60 ++---- src/services/withdrawal.py | 68 ++----- src/typings.py | 4 + src/web3py/extensions/contracts.py | 126 ++++++------ src/web3py/extensions/lido_validators.py | 28 +-- src/web3py/typings.py | 2 + tests/conftest.py | 9 + .../accounting/test_accounting_module.py | 83 ++------ .../test_safe_border_integration.py | 23 +-- .../accounting/test_safe_border_unit.py | 150 ++++++++------- .../accounting/test_validator_state.py | 179 +++++++++--------- .../accounting/test_withdrawal_integration.py | 18 +- .../accounting/test_withdrawal_unit.py | 5 +- tests/modules/ejector/test_data_encode.py | 7 +- tests/modules/ejector/test_ejector.py | 8 +- .../ejector/test_exit_order_iterator.py | 10 +- .../submodules/consensus/test_consensus.py | 3 +- .../submodules/consensus/test_reports.py | 3 +- tests/web3_extentions/test_tx_utils.py | 2 +- 44 files changed, 1408 insertions(+), 668 deletions(-) create mode 100644 src/providers/execution/__init__.py create mode 100644 src/providers/execution/base_interface.py create mode 100644 src/providers/execution/contracts/__init__.py create mode 100644 src/providers/execution/contracts/accounting_oracle.py create mode 100644 src/providers/execution/contracts/base_oracle.py create mode 100644 src/providers/execution/contracts/burner.py create mode 100644 src/providers/execution/contracts/exit_bus_oracle.py create mode 100644 src/providers/execution/contracts/hash_consensus.py create mode 100644 src/providers/execution/contracts/lido.py create mode 100644 src/providers/execution/contracts/lido_locator.py create mode 100644 src/providers/execution/contracts/oracle_daemon_config.py create mode 100644 src/providers/execution/contracts/oracle_report_sanity_checker.py create mode 100644 src/providers/execution/contracts/staking_router.py create mode 100644 src/providers/execution/contracts/withdrawal_queue_nft.py diff --git a/src/modules/accounting/accounting.py b/src/modules/accounting/accounting.py index cae79057d..dd4b4ddbb 100644 --- a/src/modules/accounting/accounting.py +++ b/src/modules/accounting/accounting.py @@ -8,9 +8,7 @@ from src.constants import SHARE_RATE_PRECISION_E27 from src.modules.accounting.typings import ( ReportData, - AccountingProcessingState, LidoReportRebase, - SharesRequestedToBurn, ) from src.metrics.prometheus.accounting import ( ACCOUNTING_IS_BUNKER, @@ -19,17 +17,17 @@ ACCOUNTING_WITHDRAWAL_VAULT_BALANCE_WEI ) from src.metrics.prometheus.duration_meter import duration_meter +from src.providers.execution.contracts.accounting_oracle import AccountingOracleContract from src.services.validator_state import LidoValidatorStateService from src.modules.submodules.consensus import ConsensusModule from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay from src.services.withdrawal import Withdrawal from src.services.bunker import BunkerService -from src.typings import BlockStamp, Gwei, ReferenceBlockStamp -from src.utils.abi import named_tuple_to_dataclass +from src.typings import BlockStamp, Gwei, ReferenceBlockStamp, StakingModuleId, NodeOperatorGlobalIndex from src.utils.cache import global_lru_cache as lru_cache from src.variables import ALLOW_REPORTING_IN_BUNKER_MODE from src.web3py.typings import Web3 -from src.web3py.extensions.lido_validators import StakingModule, NodeOperatorGlobalIndex, StakingModuleId +from src.web3py.extensions.lido_validators import StakingModule logger = logging.getLogger(__name__) @@ -50,7 +48,7 @@ class Accounting(BaseModule, ConsensusModule): CONTRACT_VERSION = 1 def __init__(self, w3: Web3): - self.report_contract = w3.lido_contracts.accounting_oracle + self.report_contract: AccountingOracleContract = w3.lido_contracts.accounting_oracle super().__init__(w3) self.lido_validator_state_service = LidoValidatorStateService(self.w3) @@ -93,9 +91,9 @@ def _submit_extra_data(self, blockstamp: ReferenceBlockStamp) -> None: extra_data = self.lido_validator_state_service.get_extra_data(blockstamp, self.get_chain_config(blockstamp)) if extra_data.extra_data: - tx = self.report_contract.functions.submitReportExtraDataList(extra_data.extra_data) + tx = self.report_contract.submit_report_extra_data_list(extra_data.extra_data) else: - tx = self.report_contract.functions.submitReportExtraDataEmpty() + tx = self.report_contract.submit_report_extra_data_empty() self.w3.transaction.check_and_send_transaction(tx, variables.ACCOUNT) @@ -108,13 +106,13 @@ def build_report(self, blockstamp: ReferenceBlockStamp) -> tuple: def is_main_data_submitted(self, blockstamp: BlockStamp) -> bool: # Consensus module: if contract got report data (second phase) - processing_state = self._get_processing_state(blockstamp) + processing_state = self.report_contract.get_processing_state(blockstamp.block_hash) logger.debug({'msg': 'Check if main data was submitted.', 'value': processing_state.main_data_submitted}) return processing_state.main_data_submitted def can_submit_extra_data(self, blockstamp: BlockStamp) -> bool: """Check if Oracle can submit extra data. Can only be submitted after second phase.""" - processing_state = self._get_processing_state(blockstamp) + processing_state = self.report_contract.get_processing_state(blockstamp.block_hash) return processing_state.main_data_submitted and not processing_state.extra_data_submitted def is_contract_reportable(self, blockstamp: BlockStamp) -> bool: @@ -132,15 +130,6 @@ def is_reporting_allowed(self, blockstamp: ReferenceBlockStamp) -> bool: logger.warning({'msg': '!' * 50}) return ALLOW_REPORTING_IN_BUNKER_MODE - @lru_cache(maxsize=1) - def _get_processing_state(self, blockstamp: BlockStamp) -> AccountingProcessingState: - ps = named_tuple_to_dataclass( - self.report_contract.functions.getProcessingState().call(block_identifier=blockstamp.block_hash), - AccountingProcessingState, - ) - logger.info({'msg': 'Fetch processing state.', 'value': ps}) - return ps - # ---------------------------------------- Build report ---------------------------------------- def _calculate_report(self, blockstamp: ReferenceBlockStamp) -> ReportData: validators_count, cl_balance = self._get_consensus_lido_state(blockstamp) @@ -260,7 +249,7 @@ def simulate_rebase_after_report( chain_conf = self.get_chain_config(blockstamp) - simulated_tx = self.w3.lido_contracts.lido.functions.handleOracleReport( + return self.w3.lido_contracts.lido.handle_oracle_report( # We use block timestamp, instead of slot timestamp, # because missed slot will break simulation contract logics # Details: https://github.com/lidofinance/lido-oracle/issues/291 @@ -273,31 +262,11 @@ def simulate_rebase_after_report( self.w3.lido_contracts.get_withdrawal_balance(blockstamp), # _withdrawalVaultBalance el_rewards, # _elRewardsVaultBalance self.get_shares_to_burn(blockstamp), # _sharesRequestedToBurn - # Decision about withdrawals processing - [], # _lastFinalizableRequestId - 0, # _simulatedShareRate - ) - - logger.info({'msg': 'Simulate lido rebase for report.', 'value': simulated_tx.args}) - - result = simulated_tx.call( - transaction={'from': self.w3.lido_contracts.accounting_oracle.address}, - block_identifier=blockstamp.block_hash, + self.w3.lido_contracts.accounting_oracle.address, ) - logger.info({'msg': 'Fetch simulated lido rebase for report.', 'value': result}) - - return LidoReportRebase(*result) - - @lru_cache(maxsize=1) def get_shares_to_burn(self, blockstamp: BlockStamp) -> int: - shares_data = named_tuple_to_dataclass( - self.w3.lido_contracts.burner.functions.getSharesRequestedToBurn().call( - block_identifier=blockstamp.block_hash, - ), - SharesRequestedToBurn, - ) - + shares_data = self.w3.lido_contracts.burner.get_shares_requested_to_burn(blockstamp.block_hash) return shares_data.cover_shares + shares_data.non_cover_shares def _get_slots_elapsed_from_last_report(self, blockstamp: ReferenceBlockStamp): diff --git a/src/modules/accounting/extra_data.py b/src/modules/accounting/extra_data.py index 8c54ec938..c4e011701 100644 --- a/src/modules/accounting/extra_data.py +++ b/src/modules/accounting/extra_data.py @@ -5,7 +5,7 @@ from hexbytes import HexBytes from src.modules.submodules.typings import ZERO_HASH -from src.web3py.extensions.lido_validators import NodeOperatorGlobalIndex +from src.typings import NodeOperatorGlobalIndex from src.web3py.typings import Web3 diff --git a/src/modules/accounting/typings.py b/src/modules/accounting/typings.py index 1b9dd86ff..d9fc6a5cb 100644 --- a/src/modules/accounting/typings.py +++ b/src/modules/accounting/typings.py @@ -4,8 +4,7 @@ from hexbytes import HexBytes from web3.types import Wei -from src.typings import SlotNumber, Gwei -from src.web3py.extensions.lido_validators import StakingModuleId +from src.typings import SlotNumber, Gwei, StakingModuleId @dataclass @@ -81,12 +80,6 @@ class LidoReportRebase: el_reward: Wei -@dataclass -class Account: - address: ChecksumAddress - _private_key: HexBytes - - @dataclass class BatchState: remaining_eth_budget: int @@ -107,3 +100,13 @@ def as_tuple(self): class SharesRequestedToBurn: cover_shares: int non_cover_shares: int + + +@dataclass +class WithdrawalRequestStatus: + amountOfStETH: int + amountOfShares: int + owner: ChecksumAddress + timestamp: int + is_finalized: bool + is_claimed: bool diff --git a/src/modules/ejector/ejector.py b/src/modules/ejector/ejector.py index edb0a21d2..6ea17f3ed 100644 --- a/src/modules/ejector/ejector.py +++ b/src/modules/ejector/ejector.py @@ -24,18 +24,18 @@ from src.modules.submodules.consensus import ConsensusModule from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay from src.providers.consensus.typings import Validator +from src.providers.execution.contracts.exit_bus_oracle import ExitBusOracleContract from src.services.exit_order_iterator import ExitOrderIterator from src.services.prediction import RewardsPredictionService from src.services.validator_state import LidoValidatorStateService -from src.typings import BlockStamp, EpochNumber, ReferenceBlockStamp -from src.utils.abi import named_tuple_to_dataclass +from src.typings import BlockStamp, EpochNumber, ReferenceBlockStamp, NodeOperatorGlobalIndex from src.utils.cache import global_lru_cache as lru_cache from src.utils.validator_state import ( is_active_validator, is_fully_withdrawable_validator, is_partially_withdrawable_validator, ) -from src.web3py.extensions.lido_validators import LidoValidator, NodeOperatorGlobalIndex +from src.web3py.extensions.lido_validators import LidoValidator from src.web3py.typings import Web3 logger = logging.getLogger(__name__) @@ -64,7 +64,8 @@ class Ejector(BaseModule, ConsensusModule): AVG_EXPECTING_WITHDRAWALS_SWEEP_DURATION_MULTIPLIER = 0.5 def __init__(self, w3: Web3): - self.report_contract = w3.lido_contracts.validators_exit_bus_oracle + self.report_contract: ExitBusOracleContract = w3.lido_contracts.validators_exit_bus_oracle + super().__init__(w3) self.prediction_service = RewardsPredictionService(w3) @@ -109,7 +110,7 @@ def build_report(self, blockstamp: ReferenceBlockStamp) -> tuple: return report_data.as_tuple() def get_validators_to_eject(self, blockstamp: ReferenceBlockStamp) -> list[tuple[NodeOperatorGlobalIndex, LidoValidator]]: - to_withdraw_amount = self.get_total_unfinalized_withdrawal_requests_amount(blockstamp) + to_withdraw_amount = self.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth(blockstamp.block_hash) logger.info({'msg': 'Calculate to withdraw amount.', 'value': to_withdraw_amount}) EJECTOR_TO_WITHDRAW_WEI_AMOUNT.set(to_withdraw_amount) @@ -185,11 +186,8 @@ def get_validators_to_eject(self, blockstamp: ReferenceBlockStamp) -> list[tuple return validators_to_eject - def _is_paused(self, blockstamp: ReferenceBlockStamp) -> bool: - return self.report_contract.functions.isPaused().call(block_identifier=blockstamp.block_hash) - def is_reporting_allowed(self, blockstamp: ReferenceBlockStamp) -> bool: - on_pause = self._is_paused(blockstamp) + on_pause = self.report_contract.is_paused(blockstamp.block_hash) CONTRACT_ON_PAUSE.labels('reporting').set(on_pause) logger.info({'msg': 'Fetch isPaused from ejector bus contract.', 'value': on_pause}) return not on_pause @@ -218,31 +216,11 @@ def _get_predicted_withdrawable_balance(self, validator: Validator) -> Wei: return self.w3.to_wei(min(int(validator.balance), MAX_EFFECTIVE_BALANCE), 'gwei') def _get_total_el_balance(self, blockstamp: BlockStamp) -> Wei: - total_el_balance = Wei( + return Wei( self.w3.lido_contracts.get_el_vault_balance(blockstamp) + self.w3.lido_contracts.get_withdrawal_balance(blockstamp) + - self._get_buffer_ether(blockstamp) + self.w3.lido_contracts.lido.get_buffered_ether(blockstamp.block_hash) ) - return total_el_balance - - def _get_buffer_ether(self, blockstamp: BlockStamp) -> Wei: - """ - The reserved buffered ether is min(current_buffered_ether, unfinalized_withdrawal_requests_amount) - We can skip calculating reserved buffer for ejector, because in case if - (unfinalized_withdrawal_requests_amount <= current_buffered_ether) - We won't eject validators at all, because we have enough eth to fulfill all requests. - """ - return Wei( - self.w3.lido_contracts.lido.functions.getBufferedEther().call( - block_identifier=blockstamp.block_hash - ) - ) - - def get_total_unfinalized_withdrawal_requests_amount(self, blockstamp: BlockStamp) -> Wei: - unfinalized_steth = self.w3.lido_contracts.withdrawal_queue_nft.functions.unfinalizedStETH().call( - block_identifier=blockstamp.block_hash, - ) - return unfinalized_steth def _get_predicted_withdrawable_epoch( self, @@ -330,16 +308,8 @@ def _get_churn_limit(self, blockstamp: ReferenceBlockStamp) -> int: logger.info({'msg': 'Calculate churn limit.', 'value': churn_limit}) return churn_limit - def _get_processing_state(self, blockstamp: BlockStamp) -> EjectorProcessingState: - ps = named_tuple_to_dataclass( - self.report_contract.functions.getProcessingState().call(block_identifier=blockstamp.block_hash), - EjectorProcessingState, - ) - logger.info({'msg': 'Fetch processing state.', 'value': ps}) - return ps - def is_main_data_submitted(self, blockstamp: BlockStamp) -> bool: - processing_state = self._get_processing_state(blockstamp) + processing_state = self.report_contract.get_processing_state(blockstamp.block_hash) return processing_state.data_submitted def is_contract_reportable(self, blockstamp: BlockStamp) -> bool: diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index a9314e617..54fa7ee1b 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -1,15 +1,17 @@ import logging from abc import ABC, abstractmethod from time import sleep -from typing import Optional +from typing import Optional, cast from eth_abi import encode -from eth_typing import ChecksumAddress +from eth_typing import ChecksumAddress, Hash32 from hexbytes import HexBytes from web3.contract import AsyncContract, Contract from src import variables from src.metrics.prometheus.basic import ORACLE_SLOT_NUMBER, ORACLE_BLOCK_NUMBER, GENESIS_TIME, ACCOUNT_BALANCE +from src.providers.execution.contracts.base_oracle import BaseOracleContract +from src.providers.execution.contracts.hash_consensus import HashConsensusContract from src.typings import BlockStamp, ReferenceBlockStamp, SlotNumber from src.metrics.prometheus.business import ( ORACLE_MEMBER_LAST_REPORT_REF_SLOT, @@ -40,7 +42,7 @@ class ConsensusModule(ABC): report_contract should contain getConsensusContract method. """ - report_contract: Contract + report_contract: BaseOracleContract CONTRACT_VERSION: int CONSENSUS_VERSION: int @@ -72,50 +74,31 @@ def check_contract_configs(self): # ----- Web3 data requests ----- @lru_cache(maxsize=1) - def _get_consensus_contract(self, blockstamp: BlockStamp) -> Contract | AsyncContract: - return self.w3.eth.contract( - address=self._get_consensus_contract_address(blockstamp), - abi=self.w3.lido_contracts.load_abi('HashConsensus'), + def _get_consensus_contract(self, blockstamp: BlockStamp) -> HashConsensusContract: + return cast(HashConsensusContract, self.w3.eth.contract( + address=self.report_contract.get_consensus_contract(blockstamp.block_hash), + ContractFactoryClass=HashConsensusContract, decode_tuples=True, - ) - - def _get_consensus_contract_address(self, blockstamp: BlockStamp) -> ChecksumAddress: - return self.report_contract.functions.getConsensusContract().call(block_identifier=blockstamp.block_hash) + )) def _get_consensus_contract_members(self, blockstamp: BlockStamp): consensus_contract = self._get_consensus_contract(blockstamp) - members, last_reported_ref_slots = consensus_contract.functions.getMembers().call(block_identifier=blockstamp.block_hash) - return members, last_reported_ref_slots + return consensus_contract.get_members(blockstamp.block_hash) @lru_cache(maxsize=1) def get_chain_config(self, blockstamp: BlockStamp) -> ChainConfig: consensus_contract = self._get_consensus_contract(blockstamp) - cc = named_tuple_to_dataclass( - consensus_contract.functions.getChainConfig().call(block_identifier=blockstamp.block_hash), - ChainConfig, - ) - logger.info({'msg': 'Fetch chain config.', 'value': cc}) - return cc + return consensus_contract.get_chain_config(blockstamp.block_hash) @lru_cache(maxsize=1) def get_current_frame(self, blockstamp: BlockStamp) -> CurrentFrame: consensus_contract = self._get_consensus_contract(blockstamp) - cf = named_tuple_to_dataclass( - consensus_contract.functions.getCurrentFrame().call(block_identifier=blockstamp.block_hash), - CurrentFrame, - ) - logger.info({'msg': 'Fetch current frame.', 'value': cf}) - return cf + return consensus_contract.get_current_frame(blockstamp.block_hash) @lru_cache(maxsize=1) def get_frame_config(self, blockstamp: BlockStamp) -> FrameConfig: consensus_contract = self._get_consensus_contract(blockstamp) - fc = named_tuple_to_dataclass( - consensus_contract.functions.getFrameConfig().call(block_identifier=blockstamp.block_hash), - FrameConfig, - ) - logger.info({'msg': 'Fetch frame config.', 'value': fc}) - return fc + return consensus_contract.get_frame_config(blockstamp.block_hash) @lru_cache(maxsize=1) def get_member_info(self, blockstamp: BlockStamp) -> MemberInfo: @@ -144,9 +127,10 @@ def get_member_info(self, blockstamp: BlockStamp) -> MemberInfo: last_member_report_ref_slot, # The hash reported by the member for the current frame, if any. current_frame_member_report, - ) = consensus_contract.functions.getConsensusStateForMember( + ) = consensus_contract.get_consensus_state_for_member( variables.ACCOUNT.address, - ).call(block_identifier=blockstamp.block_hash) + blockstamp.block_hash, + ) is_submit_member = self._is_submit_member(blockstamp) @@ -175,18 +159,12 @@ def _is_submit_member(self, blockstamp: BlockStamp) -> bool: if not variables.ACCOUNT: return True - submit_role = self.report_contract.functions.SUBMIT_DATA_ROLE().call( - block_identifier=blockstamp.block_hash, - ) - is_submit_member = self.report_contract.functions.hasRole( - submit_role, + return self.report_contract.has_role( + self.report_contract.submit_data_role(blockstamp.block_hash), variables.ACCOUNT.address, - ).call( - block_identifier=blockstamp.block_hash, + blockstamp.block_hash ) - return is_submit_member - # ----- Calculation reference slot for report ----- def get_blockstamp_for_report(self, last_finalized_blockstamp: BlockStamp) -> Optional[ReferenceBlockStamp]: """ @@ -236,8 +214,8 @@ def get_blockstamp_for_report(self, last_finalized_blockstamp: BlockStamp) -> Op return bs def _check_contract_versions(self, blockstamp: BlockStamp) -> bool: - contract_version = self.report_contract.functions.getContractVersion().call(block_identifier=blockstamp.block_hash) - consensus_version = self.report_contract.functions.getConsensusVersion().call(block_identifier=blockstamp.block_hash) + contract_version = self.report_contract.get_contract_version(blockstamp.block_hash) + consensus_version = self.report_contract.get_consensus_version(blockstamp.block_hash) if contract_version > self.CONTRACT_VERSION or consensus_version > self.CONSENSUS_VERSION: raise IncompatibleContractVersion( @@ -383,15 +361,15 @@ def _encode_data_hash(self, report_data: tuple): report_hash = self.w3.keccak(encoded) return report_hash - def _send_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: bytes, consensus_version: int): + def _send_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: Hash32, consensus_version: int): consensus_contract = self._get_consensus_contract(blockstamp) - tx = consensus_contract.functions.submitReport(blockstamp.ref_slot, report_hash, consensus_version) + tx = consensus_contract.submit_report(blockstamp.ref_slot, report_hash, consensus_version) self.w3.transaction.check_and_send_transaction(tx, variables.ACCOUNT) def _submit_report(self, report: tuple, contract_version: int): - tx = self.report_contract.functions.submitReportData(report, contract_version) + tx = self.report_contract.submit_report_data(report, contract_version) self.w3.transaction.check_and_send_transaction(tx, variables.ACCOUNT) diff --git a/src/modules/submodules/typings.py b/src/modules/submodules/typings.py index 6a9101920..b52fe9d3c 100644 --- a/src/modules/submodules/typings.py +++ b/src/modules/submodules/typings.py @@ -3,6 +3,9 @@ from src.typings import SlotNumber +ZERO_HASH = bytes([0]*32) + + @dataclass class MemberInfo: is_report_member: bool diff --git a/src/providers/execution/__init__.py b/src/providers/execution/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py new file mode 100644 index 000000000..a2bd6f08f --- /dev/null +++ b/src/providers/execution/base_interface.py @@ -0,0 +1,22 @@ +import json +from typing import Optional, Any + +from web3 import Web3 +from web3.contract import Contract + + +class ContractInterface(Contract): + abi_path: str = None + + @staticmethod + def load_abi(abi_file: str) -> dict: + with open(abi_file) as abi_json: + return json.load(abi_json) + + @classmethod + def factory(cls, web3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + if cls.abi_path is None: + raise AttributeError(f'abi_path attribute is missing in {cls.__name__} class') + + kwargs['abi'] = cls.load_abi(cls.abi_path) + return super().factory(web3, class_name, **kwargs) diff --git a/src/providers/execution/contracts/__init__.py b/src/providers/execution/contracts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py new file mode 100644 index 000000000..072104c6f --- /dev/null +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -0,0 +1,46 @@ +import logging +from functools import lru_cache + +from web3.types import TxParams, BlockIdentifier + +from src.modules.accounting.typings import AccountingProcessingState +from src.providers.execution.contracts.base_oracle import BaseOracleContract +from src.utils.abi import named_tuple_to_dataclass + + +logger = logging.getLogger(__name__) + + +class AccountingOracleContract(BaseOracleContract): + abi_path = './assets/AccountingOracle.json' + + @lru_cache(maxsize=1) + def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> AccountingProcessingState: + """ + Returns data processing state for the current reporting frame. + """ + response = self.functions.getProcessingState().call(block_identifier) + response = named_tuple_to_dataclass(response, AccountingProcessingState) + logger.info({ + 'msg': 'Call `getProcessingState()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + def submit_report_extra_data_empty(self) -> TxParams: + """ + Triggers the processing required when no extra data is present in the report, + i.e. when extra data format equals EXTRA_DATA_FORMAT_EMPTY. + """ + tx = self.functions.submitReportExtraDataEmpty() + logger.info({'msg': 'Build `submitReportExtraDataEmpty()` tx.'}) + return tx + + def submit_report_extra_data_list(self, extra_data: bytes) -> TxParams: + """ + Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. + """ + tx = self.functions.submitReportExtraDataList(extra_data) + logger.info({'msg': 'Build `submitReportExtraDataList({})` tx.'.format(extra_data)}) + return tx diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py new file mode 100644 index 000000000..996c272d3 --- /dev/null +++ b/src/providers/execution/contracts/base_oracle.py @@ -0,0 +1,115 @@ +import logging +from functools import lru_cache + +from eth_typing import ChecksumAddress, Hash32 +from web3.types import TxParams, BlockIdentifier + +from src.providers.execution.base_interface import ContractInterface +from src.typings import SlotNumber + +logger = logging.getLogger(__name__) + + +class BaseOracleContract(ContractInterface): + + @lru_cache(maxsize=1) + def get_consensus_contract(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + """ + Returns the address of the HashConsensus contract. + """ + response = self.functions.getConsensusContract().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `getConsensusContract()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def submit_data_role(self, block_identifier: BlockIdentifier = 'latest') -> Hash32: + """ + An ACL role granting the permission to submit the data for a committee report. + """ + response = self.functions.SUBMIT_DATA_ROLE().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `SUBMIT_DATA_ROLE()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def has_role(self, role: Hash32, address: ChecksumAddress, block_identifier: BlockIdentifier = 'latest') -> bool: + """ + Returns `true` if `account` has been granted `role`. + """ + response = self.functions.hasRole(role, address).call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `hasRole({}, {})`.'.format(role, address), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_contract_version(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + Returns the current contract version. + """ + response = self.functions.getContractVersion().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `getContractVersion().', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_consensus_version(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + Returns the current consensus version expected by the oracle contract. + Consensus version must change every time consensus rules change, meaning that + an oracle looking at the same reference slot would calculate a different hash. + """ + response = self.functions.getConsensusVersion().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `getConsensusVersion().', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + def submit_report_data(self, report, contract_version: int) -> TxParams: + """ + Submits report data for processing. + data. See the `ReportData` structure's docs for details. + contractVersion Expected version of the oracle contract. + + Reverts if: + - The caller is not a member of the oracle committee and doesn't possess the SUBMIT_DATA_ROLE. + - The provided contract version is different from the current one. + - The provided consensus version is different from the expected one. + - The provided reference slot differs from the current consensus frame's one. + - The processing deadline for the current consensus frame is missed. + - The keccak256 hash of the ABI-encoded data is different from the last hash provided by the hash consensus contract. + - The provided data doesn't meet safety checks. + """ + tx = self.functions.submitReportData(report.as_tuple(), contract_version) + logger.info({ + 'msg': 'Build `submitReport({}, {}) tx.'.format(report.as_tuple(), contract_version) + }) + return tx + + @lru_cache(maxsize=1) + def get_last_processing_ref_slot(self, block_identifier: BlockIdentifier = 'latest') -> SlotNumber: + """ + Returns the last reference slot for which processing of the report was started. + HashConsensus won't submit reports for any slot less than or equal to this slot. + """ + response = self.functions.getLastProcessingRefSlot().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `getLastProcessingRefSlot().', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return SlotNumber(response) diff --git a/src/providers/execution/contracts/burner.py b/src/providers/execution/contracts/burner.py new file mode 100644 index 000000000..850b057e1 --- /dev/null +++ b/src/providers/execution/contracts/burner.py @@ -0,0 +1,30 @@ +import logging +from functools import lru_cache + +from web3.types import BlockIdentifier + +from src.modules.accounting.typings import SharesRequestedToBurn +from src.providers.execution.base_interface import ContractInterface +from src.utils.abi import named_tuple_to_dataclass + + +logger = logging.getLogger(__name__) + + +class BurnerContract(ContractInterface): + abi_path = './assets/Burner.json' + + @lru_cache(maxsize=1) + def get_shares_requested_to_burn(self, block_identifier: BlockIdentifier = 'latest') -> SharesRequestedToBurn: + """ + Returns the current amount of shares locked on the contract to be burnt. + """ + response = self.functions.getSharesRequestedToBurn().call(block_identifier=block_identifier) + + response = named_tuple_to_dataclass(response, SharesRequestedToBurn) + logger.info({ + 'msg': 'Call `totalSupply()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/exit_bus_oracle.py b/src/providers/execution/contracts/exit_bus_oracle.py new file mode 100644 index 000000000..19a64e56d --- /dev/null +++ b/src/providers/execution/contracts/exit_bus_oracle.py @@ -0,0 +1,67 @@ +import logging +from functools import lru_cache +from typing import Sequence + +from web3.types import BlockIdentifier + +from src.modules.ejector.typings import EjectorProcessingState +from src.providers.execution.contracts.base_oracle import BaseOracleContract +from src.utils.abi import named_tuple_to_dataclass + + +logger = logging.getLogger(__name__) + + +class ExitBusOracleContract(BaseOracleContract): + abi_path = './assets/ValidatorsExitBusOracle.json' + + @lru_cache(maxsize=1) + def is_paused(self, block_identifier: BlockIdentifier = 'latest') -> bool: + """ + Returns whether the contract is paused. + """ + response = self.functions.isPaused().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `isPaused()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> EjectorProcessingState: + """ + Returns data processing state for the current reporting frame. + """ + response = self.functions.getProcessingState().call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, EjectorProcessingState) + logger.info({ + 'msg': 'Call `getProcessingState()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_last_requested_validator_indices( + self, + module_id: int, + node_operators_ids_in_module: Sequence[int], + block_identifier: BlockIdentifier = 'latest', + ) -> list[int]: + """ + Returns the latest validator indices that were requested to exit for the given + `nodeOpIds` in the given `moduleId`. For node operators that were never requested to exit + any validator, index is set to -1. + """ + response = self.functions.getLastRequestedValidatorIndices( + module_id, + node_operators_ids_in_module, + ).call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getLastRequestedValidatorIndices({}, {})`.'.format(module_id, node_operators_ids_in_module), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py new file mode 100644 index 000000000..974b0f50e --- /dev/null +++ b/src/providers/execution/contracts/hash_consensus.py @@ -0,0 +1,132 @@ +import logging +from functools import lru_cache + +from eth_typing import ChecksumAddress, Hash32 +from web3.types import TxParams, BlockIdentifier + +from src.modules.submodules.typings import ChainConfig, CurrentFrame, FrameConfig +from src.providers.execution.base_interface import ContractInterface +from src.utils.abi import named_tuple_to_dataclass + + +logger = logging.getLogger(__name__) + + +class HashConsensusContract(ContractInterface): + abi_path = './assets/HashConsensus.json' + + @lru_cache(maxsize=1) + def get_members(self, block_identifier: BlockIdentifier = 'latest') -> tuple[list[ChecksumAddress], list[int]]: + """ + Returns all current members, together with the last reference slot each member + submitted a report for. + """ + response = self.functions.getMembers().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getMembers()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + + return response + + @lru_cache(maxsize=1) + def get_chain_config(self, block_identifier: BlockIdentifier = 'latest') -> ChainConfig: + """ + Returns the immutable chain parameters required to calculate epoch and slot + given a timestamp. + """ + response = self.functions.getChainConfig().call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, ChainConfig) + + logger.info({ + 'msg': 'Call `getChainConfig()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + + return response + + @lru_cache(maxsize=1) + def get_current_frame(self, block_identifier: BlockIdentifier = 'latest') -> CurrentFrame: + """ + Returns the current reporting frame. + + ref_slot The frame's reference slot: if the data the consensus is being reached upon + includes or depends on any onchain state, this state should be queried at the + reference slot. If the slot contains a block, the state should include all changes + from that block. + + report_processing_deadline_slot: The last slot at which the report can be processed + by the report processor contract. + """ + response = self.functions.getCurrentFrame().call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, CurrentFrame) + + logger.info({ + 'msg': 'Call `getCurrentFrame()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + + return response + + @lru_cache(maxsize=1) + def get_frame_config(self, block_identifier: BlockIdentifier = 'latest') -> FrameConfig: + """ + Returns the time-related configuration. + + initialEpoch Epoch of the frame with zero index. + epochsPerFrame Length of a frame in epochs. + fastLaneLengthSlots Length of the fast lane interval in slots; see `getIsFastLaneMember`. + """ + response = self.functions.getFrameConfig().call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, FrameConfig) + + logger.info({ + 'msg': 'Call `getFrameConfig()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + + return response + + @lru_cache(maxsize=1) + def get_consensus_state_for_member(self, address: ChecksumAddress, block_identifier: BlockIdentifier = 'latest') -> tuple: + """ + Returns the extended information related to an oracle committee member with the + given address and the current consensus state. Provides all the information needed for + an oracle daemon to decide if it needs to submit a report. + """ + response = self.functions.getConsensusStateForMember(address).call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getFrameConfig()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + + return response + + def submit_report(self, ref_slot: int, report_hash: Hash32, consensus_version: int) -> TxParams: + """ + Used by oracle members to submit hash of the data calculated for the given reference slot. + + ref_slot: The reference slot the data was calculated for. Reverts if doesn't match + the current reference slot. + + report_hash: of the data calculated for the given reference slot. + + consensus_version: Version of the oracle consensus rules. Reverts if doesn't + match the version returned by the currently set consensus report processor, + or zero if no report processor is set. + """ + tx = self.functions.submitReport(ref_slot, report_hash, consensus_version) + + logger.info({ + 'msg': 'Build `submitReport({}, {}, {})`.'.format(ref_slot, report_hash, consensus_version), + }) + + return tx + diff --git a/src/providers/execution/contracts/lido.py b/src/providers/execution/contracts/lido.py new file mode 100644 index 000000000..f6fcffd26 --- /dev/null +++ b/src/providers/execution/contracts/lido.py @@ -0,0 +1,101 @@ +import logging + +from eth_typing import ChecksumAddress +from web3.types import Gwei, Wei, BlockIdentifier + +from src.modules.accounting.typings import LidoReportRebase +from src.providers.execution.base_interface import ContractInterface + + +logger = logging.getLogger(__name__) + + +class LidoContract(ContractInterface): + abi_path = './assets/Lido.json' + + def handle_oracle_report( + self, + timestamp: int, + time_elapsed: int, + validators_count: int, + cl_balance: Wei, + withdrawal_vault_balance: Wei, + el_rewards: Wei, + shares_to_burn: int, + accounting_oracle_address: ChecksumAddress, + block_identifier: BlockIdentifier = 'latest', + ) -> LidoReportRebase: + """ + Updates accounting stats, collects EL rewards and distributes collected rewards + if beacon balance increased, performs withdrawal requests finalization + periodically called by the AccountingOracle contract + + NB: `_simulatedShareRate` should be calculated off-chain by calling the method with `eth_call` JSON-RPC API + while passing empty `_withdrawalFinalizationBatches` and `_simulatedShareRate` == 0, plugging the returned values + to the following formula: `_simulatedShareRate = (postTotalPooledEther * 1e27) / postTotalShares` + """ + response = self.functions.handleOracleReport( + timestamp, + time_elapsed, + validators_count, + cl_balance, + withdrawal_vault_balance, + el_rewards, + shares_to_burn, + [], + 0, + ).call( + transaction={'from': accounting_oracle_address}, + block_identifier=block_identifier, + ) + + response = LidoReportRebase(*response) + + logger.info({ + 'msg': 'Call `handleOracleReport({}, {}, {}, {}, {}, {}, {}, {}, {})`.'.format( + timestamp, + time_elapsed, + validators_count, + cl_balance, + withdrawal_vault_balance, + el_rewards, + shares_to_burn, + [], + 0, + ), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + def get_buffered_ether(self, block_identifier: BlockIdentifier = 'latest') -> Wei: + """ + Get the amount of Ether temporary buffered on this contract balance + Buffered balance is kept on the contract from the moment the funds are received from user + until the moment they are actually sent to the official Deposit contract. + return amount of buffered funds in wei + """ + response = self.functions.getBufferedEther().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getBufferedEther()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return Wei(response) + + def total_supply(self, block_identifier: BlockIdentifier = 'latest') -> Wei: + """ + return the amount of tokens in existence. + + Always equals to `_getTotalPooledEther()` since token amount + is pegged to the total amount of Ether controlled by the protocol. + """ + response = self.functions.totalSupply().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `totalSupply()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return Wei(response) \ No newline at end of file diff --git a/src/providers/execution/contracts/lido_locator.py b/src/providers/execution/contracts/lido_locator.py new file mode 100644 index 000000000..d28c79041 --- /dev/null +++ b/src/providers/execution/contracts/lido_locator.py @@ -0,0 +1,124 @@ +import logging +from functools import lru_cache + +from eth_typing import ChecksumAddress +from web3.types import BlockIdentifier + +from src.providers.execution.base_interface import ContractInterface + + +logger = logging.getLogger(__name__) + + +class LidoLocatorContract(ContractInterface): + abi_path = './assets/LidoLocator.json' + + @lru_cache(maxsize=1) + def lido(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.lido().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `lido()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def accounting_oracle(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.accountingOracle().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `accountingOracle()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def staking_router(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.stakingRouter().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `stakingRouter()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def validator_exit_bus_oracle(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.validatorsExitBusOracle().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `validatorsExitBusOracle()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def withdrawal_queue(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.withdrawalQueue().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `withdrawalQueue()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def oracle_report_sanity_checker(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.oracleReportSanityChecker().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `oracleReportSanityChecker()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def oracle_daemon_config(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.oracleDaemonConfig().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `oracleDaemonConfig()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def burner(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.burner().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `burner()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def withdrawal_vault(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.withdrawalVault().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `withdrawalVault()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def el_rewards_vault(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: + response = self.functions.elRewardsVault().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `elRewardsVault()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/oracle_daemon_config.py b/src/providers/execution/contracts/oracle_daemon_config.py new file mode 100644 index 000000000..eb88c3606 --- /dev/null +++ b/src/providers/execution/contracts/oracle_daemon_config.py @@ -0,0 +1,121 @@ +import logging +from functools import lru_cache + +from web3 import Web3 +from web3.types import BlockIdentifier + +from src.providers.execution.base_interface import ContractInterface + + +logger = logging.getLogger(__name__) + + +class OracleDaemonConfigContract(ContractInterface): + abi_path = './assets/OracleDaemonConfig.json' + + @lru_cache(maxsize=1) + def normalized_cl_reward_per_epoch(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('NORMALIZED_CL_REWARD_PER_EPOCH').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `NORMALIZED_CL_REWARD_PER_EPOCH()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def normalized_cl_reward_mistake_rate_bp(self, block_identifier: BlockIdentifier = 'latest') -> float: + response = self.functions.get('NORMALIZED_CL_REWARD_MISTAKE_RATE_BP').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `NORMALIZED_CL_REWARD_MISTAKE_RATE_BP()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def rebase_check_nearest_epoch_distance(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('REBASE_CHECK_NEAREST_EPOCH_DISTANCE').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `REBASE_CHECK_NEAREST_EPOCH_DISTANCE()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def rebase_check_distant_epoch_distance(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('REBASE_CHECK_DISTANT_EPOCH_DISTANCE').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `REBASE_CHECK_DISTANT_EPOCH_DISTANCE()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def node_operator_network_penetration_threshold_bp(self, block_identifier: BlockIdentifier = 'latest') -> float: + response = self.functions.get('NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def prediction_duration_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('PREDICTION_DURATION_IN_SLOTS').call(block_identifier=block_identifier) + + response = Web3.to_int(response) + logger.info({ + 'msg': 'Call `PREDICTION_DURATION_IN_SLOTS()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def finalization_max_negative_rebase_epoch_shift(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT').call(block_identifier=block_identifier) + + response = Web3.to_int(primitive=response) + + logger.info({ + 'msg': 'Call `FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def validator_delayed_timeout_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS').call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def validator_delinquent_timeout_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self.functions.get('VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS').call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/oracle_report_sanity_checker.py b/src/providers/execution/contracts/oracle_report_sanity_checker.py new file mode 100644 index 000000000..eae47d849 --- /dev/null +++ b/src/providers/execution/contracts/oracle_report_sanity_checker.py @@ -0,0 +1,30 @@ +import logging +from functools import lru_cache + +from web3.types import BlockIdentifier + +from src.modules.accounting.typings import OracleReportLimits +from src.providers.execution.base_interface import ContractInterface +from src.utils.abi import named_tuple_to_dataclass + + +logger = logging.getLogger(__name__) + + +class OracleReportSanityCheckerContract(ContractInterface): + abi_path = './assets/OracleReportSanityChecker.json' + + @lru_cache(maxsize=1) + def get_oracle_report_limits(self, block_identifier: BlockIdentifier = 'latest') -> OracleReportLimits: + """ + Returns the limits list for the Lido's oracle report sanity checks + """ + response = self.functions.getOracleReportLimits().call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, OracleReportLimits) + + logger.info({ + 'msg': 'Call `getOracleReportLimits()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/staking_router.py b/src/providers/execution/contracts/staking_router.py new file mode 100644 index 000000000..ab889864e --- /dev/null +++ b/src/providers/execution/contracts/staking_router.py @@ -0,0 +1,43 @@ +import logging +from functools import lru_cache + +from web3.types import BlockIdentifier + +from src.providers.execution.base_interface import ContractInterface +from src.utils.dataclass import list_of_dataclasses +from src.web3py.extensions.lido_validators import StakingModule, NodeOperator + +logger = logging.getLogger(__name__) + + +class StakingRouterContract(ContractInterface): + abi_path = './assets/StakingRouter.json' + + @lru_cache(maxsize=1) + @list_of_dataclasses(StakingModule) + def get_staking_modules(self, block_identifier: BlockIdentifier = 'latest') -> list[StakingModule]: + """ + Returns all registered staking modules + """ + response = self.functions.getStakingModules().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getStakingModules()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_all_node_operator_digests(self, module_id: int, block_identifier: BlockIdentifier = 'latest') -> list[NodeOperator]: + """ + Returns node operator digest for each node operator in lido protocol + """ + response = self.functions.getAllNodeOperatorDigests(module_id).call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getAllNodeOperatorDigests({})`.'.format(module_id), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/providers/execution/contracts/withdrawal_queue_nft.py b/src/providers/execution/contracts/withdrawal_queue_nft.py new file mode 100644 index 000000000..56b118626 --- /dev/null +++ b/src/providers/execution/contracts/withdrawal_queue_nft.py @@ -0,0 +1,150 @@ +import logging +from functools import lru_cache + +from web3.types import Wei, BlockIdentifier + +from src.modules.accounting.typings import BatchState, WithdrawalRequestStatus +from src.providers.execution.base_interface import ContractInterface +from src.utils.abi import named_tuple_to_dataclass + +logger = logging.getLogger(__name__) + + +class WithdrawalQueueNftContract(ContractInterface): + abi_path = './assets/WithdrawalQueueERC721.json' + + @lru_cache(maxsize=1) + def unfinalized_steth(self, block_identifier: BlockIdentifier = 'latest') -> Wei: + """ + Returns the amount of stETH in the queue yet to be finalized + """ + response = self.functions.unfinalizedStETH().call(block_identifier=block_identifier) + logger.info({ + 'msg': 'Call `unfinalizedStETH()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return Wei(response) + + @lru_cache(maxsize=1) + def bunker_mode_since_timestamp(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + Get bunker mode activation timestamp. + + returns `BUNKER_MODE_DISABLED_TIMESTAMP` if bunker mode is disable (i.e., protocol in turbo mode) + """ + response = self.functions.bunkerModeSinceTimestamp().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `bunkerModeSinceTimestamp()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_last_finalized_request_id(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + id of the last finalized request + NB! requests are indexed from 1, so it returns 0 if there is no finalized requests in the queue + """ + response = self.functions.getLastFinalizedRequestId().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getLastFinalizedRequestId()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_withdrawal_status(self, request_id: int, block_identifier: BlockIdentifier = 'latest') -> WithdrawalRequestStatus: + """ + Returns status for requests with provided ids + request_id: id of request to check status + """ + response = self.functions.getWithdrawalStatus([request_id]).call(block_identifier=block_identifier) + response = named_tuple_to_dataclass(response, WithdrawalRequestStatus) + + logger.info({ + 'msg': 'Call `getWithdrawalStatus({})`.'.format([request_id]), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def get_last_request_id(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + returns id of the last request + NB! requests are indexed from 1, so it returns 0 if there is no requests in the queue + """ + response = self.functions.getLastRequestId().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `getLastRequestId()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def is_paused(self, block_identifier: BlockIdentifier = 'latest') -> bool: + """ + Returns whether the contract is paused + """ + response = self.functions.isPaused().call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `isPaused()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def max_batches_length(self, block_identifier: BlockIdentifier = 'latest') -> int: + """ + maximal length of the batch array provided for prefinalization. + """ + response = self.functions.MAX_BATCHES_LENGTH().call() + + logger.info({ + 'msg': 'Call `MAX_BATCHES_LENGTH()`.', + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response + + @lru_cache(maxsize=1) + def calculate_finalization_batches( + self, + share_rate: int, + timestamp: int, + max_batch_request_count: int, + batch_state: tuple, + block_identifier: BlockIdentifier = 'latest', + ) -> BatchState: + """ + Offchain view for the oracle daemon that calculates how many requests can be finalized within + the given budget, time period and share rate limits. Returned requests are split into batches. + Each batch consist of the requests that all have the share rate below the `_maxShareRate` or above it. + """ + response = self.functions.calculateFinalizationBatches( + share_rate, + timestamp, + max_batch_request_count, + batch_state, + ).call(block_identifier=block_identifier) + + logger.info({ + 'msg': 'Call `calculateFinalizationBatches({}, {}, {}, {})`.'.format( + share_rate, + timestamp, + max_batch_request_count, + batch_state, + ), + 'value': response, + 'block_identifier': block_identifier.__repr__(), + }) + return response diff --git a/src/services/bunker.py b/src/services/bunker.py index f19d01066..05aa2c7fb 100644 --- a/src/services/bunker.py +++ b/src/services/bunker.py @@ -91,30 +91,19 @@ def get_cl_rebase_for_current_report(self, blockstamp: BlockStamp, simulated_cl_ Get simulated Cl rebase and subtract total supply before report """ logger.info({"msg": "Getting CL rebase for frame"}) - before_report_total_pooled_ether = self._get_total_supply(blockstamp) + before_report_total_pooled_ether = self.w3.lido_contracts.lido.total_supply(blockstamp.block_hash) # Can't use from_wei - because rebase can be negative frame_cl_rebase = (simulated_cl_rebase.post_total_pooled_ether - before_report_total_pooled_ether) // GWEI_TO_WEI logger.info({"msg": f"Simulated CL rebase for frame: {frame_cl_rebase} Gwei"}) return Gwei(frame_cl_rebase) - def _get_total_supply(self, blockstamp: BlockStamp) -> Gwei: - return self.w3.lido_contracts.lido.functions.totalSupply().call(block_identifier=blockstamp.block_hash) - def _get_config(self, blockstamp: BlockStamp) -> BunkerConfig: """Get config values from OracleDaemonConfig contract""" config = self.w3.lido_contracts.oracle_daemon_config return BunkerConfig( - Web3.to_int( - config.functions.get('NORMALIZED_CL_REWARD_PER_EPOCH').call(block_identifier=blockstamp.block_hash) - ), - Web3.to_int( - config.functions.get('NORMALIZED_CL_REWARD_MISTAKE_RATE_BP').call(block_identifier=blockstamp.block_hash) - ) / TOTAL_BASIS_POINTS, - Web3.to_int( - config.functions.get('REBASE_CHECK_NEAREST_EPOCH_DISTANCE').call(block_identifier=blockstamp.block_hash) - ), - Web3.to_int( - config.functions.get('REBASE_CHECK_DISTANT_EPOCH_DISTANCE').call(block_identifier=blockstamp.block_hash) - ) + config.normalized_cl_reward_per_epoch(blockstamp.block_hash), + config.normalized_cl_reward_mistake_rate_bp(blockstamp.block_hash) / TOTAL_BASIS_POINTS, + config.rebase_check_nearest_epoch_distance(blockstamp.block_hash), + config.rebase_check_distant_epoch_distance(blockstamp.block_hash), ) diff --git a/src/services/exit_order_iterator.py b/src/services/exit_order_iterator.py index b3b9716b6..3822986ac 100644 --- a/src/services/exit_order_iterator.py +++ b/src/services/exit_order_iterator.py @@ -7,15 +7,10 @@ from src.metrics.prometheus.duration_meter import duration_meter from src.modules.submodules.typings import ChainConfig from src.services.exit_order_iterator_state import ExitOrderIteratorStateService, NodeOperatorPredictableState -from src.typings import ReferenceBlockStamp +from src.typings import ReferenceBlockStamp, NodeOperatorGlobalIndex, StakingModuleId, NodeOperatorId from src.utils.validator_state import get_validator_age -from src.web3py.extensions.lido_validators import ( - LidoValidator, - NodeOperatorGlobalIndex, - NodeOperatorId, - StakingModuleId, -) +from src.web3py.extensions.lido_validators import LidoValidator from src.web3py.typings import Web3 logger = logging.getLogger(__name__) @@ -59,7 +54,9 @@ def __iter__(self) -> Iterator[tuple[NodeOperatorGlobalIndex, LidoValidator]]: eois = ExitOrderIteratorStateService(self.w3, self.blockstamp) self.left_queue_count = 0 - self.max_validators_to_exit = eois.get_oracle_report_limits(self.blockstamp).max_validator_exit_requests_per_report + self.max_validators_to_exit = eois.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits( + self.blockstamp.block_hash, + ).max_validator_exit_requests_per_report self.operator_network_penetration_threshold = eois.get_operator_network_penetration_threshold(self.blockstamp) # Prepare list of exitable validators, which will be sorted by exit order predicates diff --git a/src/services/exit_order_iterator_state.py b/src/services/exit_order_iterator_state.py index 8ed7863f6..d23e6b87c 100644 --- a/src/services/exit_order_iterator_state.py +++ b/src/services/exit_order_iterator_state.py @@ -108,11 +108,8 @@ def get_total_predictable_validators_count( ) def get_operator_network_penetration_threshold(self, blockstamp: ReferenceBlockStamp) -> float: - exiting_keys_delayed_border_in_slots_bytes = self.w3.lido_contracts.oracle_daemon_config.functions.get( - 'NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP' - ).call(block_identifier=blockstamp.block_hash) - - return self.w3.to_int(exiting_keys_delayed_border_in_slots_bytes) / TOTAL_BASIS_POINTS + exiting_keys_delayed_border_in_slots_bytes = self.w3.lido_contracts.oracle_daemon_config.node_operator_network_penetration_threshold_bp(blockstamp.block_hash) + return exiting_keys_delayed_border_in_slots_bytes / TOTAL_BASIS_POINTS @staticmethod def count_operator_validators_stats( diff --git a/src/services/prediction.py b/src/services/prediction.py index 37b3aa979..91ac3238e 100644 --- a/src/services/prediction.py +++ b/src/services/prediction.py @@ -30,8 +30,7 @@ def get_rewards_per_epoch( blockstamp: ReferenceBlockStamp, chain_configs: ChainConfig, ) -> Wei: - prediction_duration_in_slots = self._get_prediction_duration_in_slots(blockstamp) - logger.info({'msg': 'Fetch prediction frame in slots.', 'value': prediction_duration_in_slots}) + prediction_duration_in_slots = self.w3.lido_contracts.oracle_daemon_config.prediction_duration_in_slots(blockstamp.block_hash) token_rebase_events = get_events_in_past( self.w3.lido_contracts.lido.events.TokenRebased, # type: ignore[arg-type] @@ -102,10 +101,3 @@ def _group_events_by_transaction_hash(event_type_1: list[EventData], event_type_ raise InconsistentEvents('Events are inconsistent: unexpected events_type_1 amount.') return result_event_data - - def _get_prediction_duration_in_slots(self, blockstamp: ReferenceBlockStamp) -> int: - return Web3.to_int( - self.w3.lido_contracts.oracle_daemon_config.functions.get('PREDICTION_DURATION_IN_SLOTS').call( - block_identifier=blockstamp.block_hash, - ) - ) diff --git a/src/services/safe_border.py b/src/services/safe_border.py index a52a8546d..9d0063739 100644 --- a/src/services/safe_border.py +++ b/src/services/safe_border.py @@ -34,7 +34,6 @@ class SafeBorder(Web3Converter): 2. Negative rebase border 3. Associated slashing border """ - chain_config: ChainConfig frame_config: FrameConfig blockstamp: ReferenceBlockStamp @@ -58,7 +57,11 @@ def __init__( self.converter = Web3Converter(chain_config, frame_config) - self._retrieve_constants() + limits_list = self.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits(self.blockstamp.block_hash) + + self.finalization_default_shift = math.ceil( + limits_list.request_timestamp_margin / (self.chain_config.slots_per_epoch * self.chain_config.seconds_per_slot) + ) @duration_meter() def get_safe_border_epoch( @@ -91,8 +94,9 @@ def _get_negative_rebase_border_epoch(self) -> EpochNumber: bunker_start_or_last_successful_report_epoch = self._get_bunker_start_or_last_successful_report_epoch() latest_allowable_epoch = bunker_start_or_last_successful_report_epoch - self.finalization_default_shift - earliest_allowable_epoch = self.get_epoch_by_slot( - self.blockstamp.ref_slot) - self.finalization_max_negative_rebase_shift + + max_negative_rebase = self.w3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift(self.blockstamp.block_hash) + earliest_allowable_epoch = self.get_epoch_by_slot(self.blockstamp.ref_slot) - max_negative_rebase return EpochNumber(max(earliest_allowable_epoch, latest_allowable_epoch)) @@ -240,7 +244,7 @@ def _get_validators_earliest_activation_epoch(self, validators: list[Validator]) return EpochNumber(int(sorted_validators[0].validator.activation_epoch)) def _get_bunker_mode_start_timestamp(self) -> Optional[int]: - start_timestamp = self._get_bunker_start_timestamp() + start_timestamp = self.w3.lido_contracts.withdrawal_queue_nft.bunker_mode_since_timestamp(self.blockstamp.block_hash) if start_timestamp > self.blockstamp.block_timestamp: return None @@ -248,57 +252,18 @@ def _get_bunker_mode_start_timestamp(self) -> Optional[int]: return start_timestamp def _get_last_finalized_withdrawal_request_slot(self) -> SlotNumber: - last_finalized_request_id = self._get_last_finalized_request_id() + last_finalized_request_id = self.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id(self.blockstamp.block_hash) if last_finalized_request_id == 0: # request with id: 0 is reserved by protocol. No requests were finalized. return SlotNumber(0) - last_finalized_request_data = self._get_withdrawal_request_status(last_finalized_request_id) + last_finalized_request_data = self.w3.lido_contracts.withdrawal_queue_nft.get_withdrawal_status(last_finalized_request_id) return self.get_epoch_first_slot(self.get_epoch_by_timestamp(last_finalized_request_data.timestamp)) def _get_blockstamp(self, last_slot_in_frame: SlotNumber): return get_blockstamp(self.w3.cc, last_slot_in_frame, self.blockstamp.ref_slot) - def _retrieve_constants(self): - limits_list = self._fetch_oracle_report_limits_list() - self.finalization_default_shift = math.ceil( - limits_list.request_timestamp_margin / (self.chain_config.slots_per_epoch * self.chain_config.seconds_per_slot) - ) - - self.finalization_max_negative_rebase_shift = self._fetch_finalization_max_negative_rebase_epoch_shift() - - def _fetch_oracle_report_limits_list(self): - return named_tuple_to_dataclass( - self.w3.lido_contracts.oracle_report_sanity_checker.functions.getOracleReportLimits().call( - block_identifier=self.blockstamp.block_hash - ), - OracleReportLimits - ) - - def _fetch_finalization_max_negative_rebase_epoch_shift(self): - return self.w3.to_int( - primitive=self.w3.lido_contracts.oracle_daemon_config.functions.get( - 'FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT', - ).call(block_identifier=self.blockstamp.block_hash) - ) - - def _get_bunker_start_timestamp(self) -> int: - # If bunker mode is off returns max(uint256) - return self.w3.lido_contracts.withdrawal_queue_nft.functions.bunkerModeSinceTimestamp().call( - block_identifier=self.blockstamp.block_hash - ) - - def _get_last_finalized_request_id(self) -> int: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.getLastFinalizedRequestId().call( - block_identifier=self.blockstamp.block_hash - ) - - def _get_withdrawal_request_status(self, request_id: int) -> Any: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.getWithdrawalStatus([request_id]).call( - block_identifier=self.blockstamp.block_hash - )[0] - def round_slot_by_frame(self, slot: SlotNumber) -> SlotNumber: rounded_epoch = self.round_epoch_by_frame(self.get_epoch_by_slot(slot)) return self.get_epoch_first_slot(rounded_epoch) diff --git a/src/services/validator_state.py b/src/services/validator_state.py index 38d623430..fe790e2e0 100644 --- a/src/services/validator_state.py +++ b/src/services/validator_state.py @@ -43,7 +43,7 @@ def get_extra_data(self, blockstamp: ReferenceBlockStamp, chain_config: ChainCon logger.info({'msg': 'Calculate stuck validators.', 'value': stuck_validators}) exited_validators = self.get_lido_newly_exited_validators(blockstamp) logger.info({'msg': 'Calculate exited validators.', 'value': exited_validators}) - orl = self.get_oracle_report_limits(blockstamp) + orl = self.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits(blockstamp.block_hash) extra_data = self.extra_data_service.collect( stuck_validators=stuck_validators, @@ -76,7 +76,7 @@ def sum_stuck_validators(total: int, validator: LidoValidator) -> int: return total validator_available_to_exit_epoch = int(validator.validator.activation_epoch) + SHARD_COMMITTEE_PERIOD - delinquent_timeout_in_slots = self.get_validator_delinquent_timeout_in_slot(blockstamp) + delinquent_timeout_in_slots = self.w3.lido_contracts.oracle_daemon_config.validator_delinquent_timeout_in_slots(blockstamp.block_hash) last_slot_to_exit = validator_available_to_exit_epoch * chain_config.slots_per_epoch + delinquent_timeout_in_slots @@ -104,7 +104,7 @@ def sum_stuck_validators(total: int, validator: LidoValidator) -> int: return result def get_last_requested_to_exit_pubkeys(self, blockstamp: ReferenceBlockStamp, chain_config: ChainConfig) -> set[HexStr]: - exiting_keys_stuck_border_in_slots = self.get_validator_delinquent_timeout_in_slot(blockstamp) + exiting_keys_stuck_border_in_slots = self.w3.lido_contracts.oracle_daemon_config.validator_delinquent_timeout_in_slots(blockstamp.block_hash) events = get_events_in_past( self.w3.lido_contracts.validators_exit_bus_oracle.events.ValidatorExitRequest, # type: ignore[arg-type] @@ -117,27 +117,24 @@ def get_last_requested_to_exit_pubkeys(self, blockstamp: ReferenceBlockStamp, ch return set(bytes_to_hex_str(event['args']['validatorPubkey']) for event in events) - @lru_cache(maxsize=1) - def get_validator_delinquent_timeout_in_slot(self, blockstamp: ReferenceBlockStamp) -> int: - exiting_keys_stuck_border_in_slots_bytes = self.w3.lido_contracts.oracle_daemon_config.functions.get( - 'VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS' - ).call(block_identifier=blockstamp.block_hash) - - return self.w3.to_int(exiting_keys_stuck_border_in_slots_bytes) - def get_operators_with_last_exited_validator_indexes(self, blockstamp: BlockStamp) -> dict[NodeOperatorGlobalIndex, int]: - node_operators = self.w3.lido_validators.get_lido_node_operators(blockstamp) - staking_modules = self.w3.lido_validators.get_staking_modules(blockstamp) - result = {} + staking_modules = self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash) + for module in staking_modules: - node_operators_ids_in_module = list(map(lambda op: op.id, filter(lambda operator: operator.staking_module.id == module.id, node_operators))) + node_operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module.id, blockstamp.block_hash) - last_requested_validators = self._get_last_requested_validator_indices(blockstamp, module, node_operators_ids_in_module) + last_requested_ids = self.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices( + module.id, + [no.id for no in node_operators], + blockstamp.block_hash, + ) - for no_id, validator_index in zip(node_operators_ids_in_module, last_requested_validators): - result[(module.id, no_id)] = validator_index + result.update({ + (module.id, no.id): last_requested_id + for no, last_requested_id in zip(node_operators, last_requested_ids) + }) return result @@ -171,20 +168,6 @@ def get_exited_lido_validators(self, blockstamp: ReferenceBlockStamp) -> dict[No return result - def get_oracle_report_limits(self, blockstamp: BlockStamp) -> OracleReportLimits: - result = self.w3.lido_contracts.oracle_report_sanity_checker.functions.getOracleReportLimits().call( - block_identifier=blockstamp.block_hash, - ) - orl = named_tuple_to_dataclass(result, OracleReportLimits) - logger.info({'msg': 'Fetch oracle sanity checks.', 'value': orl}) - return orl - - def _get_last_requested_validator_indices(self, blockstamp: BlockStamp, module: StakingModule, node_operators_ids_in_module: Sequence[int]) -> list[int]: - return self.w3.lido_contracts.validators_exit_bus_oracle.functions.getLastRequestedValidatorIndices( - module.id, - node_operators_ids_in_module, - ).call(block_identifier=blockstamp.block_hash) - def get_recently_requested_but_not_exited_validators( self, blockstamp: ReferenceBlockStamp, @@ -216,7 +199,8 @@ def validator_recently_requested_to_exit(validator: LidoValidator) -> bool: return int(validator.index) in recent_indexes[global_index] def validator_eligible_to_exit(validator: LidoValidator) -> bool: - delayed_timeout_in_epoch = self.get_validator_delayed_timeout_in_slot(blockstamp) // chain_config.slots_per_epoch + vals_delayed = self.w3.lido_contracts.oracle_daemon_config.validator_delayed_timeout_in_slots(blockstamp.block_hash) + delayed_timeout_in_epoch = vals_delayed // chain_config.slots_per_epoch return is_validator_eligible_to_exit(validator, EpochNumber(blockstamp.ref_epoch - delayed_timeout_in_epoch)) def is_validator_recently_requested_but_not_exited(validator: LidoValidator) -> bool: @@ -256,7 +240,7 @@ def get_recently_requests_to_exit_indexes_by_operators( chain_config: ChainConfig, operator_global_indexes: Iterable[NodeOperatorGlobalIndex], ) -> dict[NodeOperatorGlobalIndex, set[int]]: - exiting_keys_delayed_border_in_slots = self.get_validator_delayed_timeout_in_slot(blockstamp) + exiting_keys_delayed_border_in_slots = self.w3.lido_contracts.oracle_daemon_config.validator_delayed_timeout_in_slots(blockstamp.block_hash) events = get_events_in_past( self.w3.lido_contracts.validators_exit_bus_oracle.events.ValidatorExitRequest, # type: ignore[arg-type] @@ -277,11 +261,3 @@ def get_recently_requests_to_exit_indexes_by_operators( global_indexes[operator_global_index].add(event['args']['validatorIndex']) return global_indexes - - @lru_cache(maxsize=1) - def get_validator_delayed_timeout_in_slot(self, blockstamp: ReferenceBlockStamp) -> int: - exiting_keys_delayed_border_in_slots_bytes = self.w3.lido_contracts.oracle_daemon_config.functions.get( - 'VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS' - ).call(block_identifier=blockstamp.block_hash) - - return self.w3.to_int(exiting_keys_delayed_border_in_slots_bytes) diff --git a/src/services/withdrawal.py b/src/services/withdrawal.py index c0cd77ea4..16c6bc012 100644 --- a/src/services/withdrawal.py +++ b/src/services/withdrawal.py @@ -2,7 +2,6 @@ from src.metrics.prometheus.business import CONTRACT_ON_PAUSE from src.variables import FINALIZATION_BATCH_MAX_REQUEST_COUNT -from src.utils.abi import named_tuple_to_dataclass from src.web3py.typings import Web3 from src.typings import ReferenceBlockStamp from src.services.safe_border import SafeBorder @@ -38,7 +37,7 @@ def get_finalization_batches( withdrawal_vault_balance: Wei, el_rewards_vault_balance: Wei ) -> list[int]: - on_pause = self._is_requests_finalization_paused() + on_pause = self.w3.lido_contracts.withdrawal_queue_nft.is_paused(self.blockstamp.block_hash) CONTRACT_ON_PAUSE.labels('finalization').set(on_pause) if on_pause: return [] @@ -57,76 +56,43 @@ def get_finalization_batches( return self._calculate_finalization_batches(share_rate, available_eth, withdrawable_until_timestamp) def _has_unfinalized_requests(self) -> bool: - last_finalized_id = self._fetch_last_finalized_request_id() - last_requested_id = self._fetch_last_request_id() + last_finalized_id = self.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id(self.blockstamp.block_hash) + last_requested_id = self.w3.lido_contracts.withdrawal_queue_nft.get_last_request_id(self.blockstamp.block_hash) return last_finalized_id < last_requested_id def _get_available_eth(self, withdrawal_vault_balance: Wei, el_rewards_vault_balance: Wei) -> Wei: - buffered_ether = self._fetch_buffered_ether() + buffered_ether = self.w3.lido_contracts.lido.get_buffered_ether(self.blockstamp.block_hash) + # This amount of eth could not be spent for deposits. - unfinalized_steth = self._fetch_unfinalized_steth() + unfinalized_steth = self.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth(self.blockstamp.block_hash) reserved_buffer = min(buffered_ether, unfinalized_steth) return Wei(withdrawal_vault_balance + el_rewards_vault_balance + reserved_buffer) def _calculate_finalization_batches( - self, share_rate: int, available_eth: int, until_timestamp: int + self, + share_rate: int, + available_eth: int, + until_timestamp: int ) -> list[int]: + max_length = self.w3.lido_contracts.withdrawal_queue_nft.max_batches_length(self.blockstamp.block_hash) + state = BatchState( remaining_eth_budget=available_eth, finished=False, - batches=[0] * self._fetch_max_batches_length(), + batches=[0] * max_length, batches_length=0 ) while not state.finished: - state = self._fetch_finalization_batches( + state = self.w3.lido_contracts.withdrawal_queue_nft.calculate_finalization_batches( share_rate, until_timestamp, - state + FINALIZATION_BATCH_MAX_REQUEST_COUNT, + state.as_tuple(), + self.blockstamp.block_hash ) return list(filter(lambda value: value > 0, state.batches)) - - def _fetch_last_finalized_request_id(self) -> int: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.getLastFinalizedRequestId().call( - block_identifier=self.blockstamp.block_hash - ) - - def _fetch_last_request_id(self) -> int: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.getLastRequestId().call( - block_identifier=self.blockstamp.block_hash - ) - - def _fetch_buffered_ether(self) -> Wei: - return Wei(self.w3.lido_contracts.lido.functions.getBufferedEther().call( - block_identifier=self.blockstamp.block_hash - )) - - def _fetch_unfinalized_steth(self) -> Wei: - return Wei(self.w3.lido_contracts.withdrawal_queue_nft.functions.unfinalizedStETH().call( - block_identifier=self.blockstamp.block_hash - )) - - def _is_requests_finalization_paused(self) -> bool: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.isPaused().call( - block_identifier=self.blockstamp.block_hash - ) - - def _fetch_max_batches_length(self) -> int: - return self.w3.lido_contracts.withdrawal_queue_nft.functions.MAX_BATCHES_LENGTH().call( - block_identifier=self.blockstamp.block_hash - ) - - def _fetch_finalization_batches(self, share_rate: int, timestamp: int, batch_state: BatchState) -> BatchState: - return named_tuple_to_dataclass( - self.w3.lido_contracts.withdrawal_queue_nft.functions.calculateFinalizationBatches( - share_rate, - timestamp, - FINALIZATION_BATCH_MAX_REQUEST_COUNT, - batch_state.as_tuple() - ).call(block_identifier=self.blockstamp.block_hash), - BatchState - ) diff --git a/src/typings.py b/src/typings.py index 38ce331c1..07f23aff6 100644 --- a/src/typings.py +++ b/src/typings.py @@ -18,6 +18,10 @@ class OracleModule(StrEnum): BlockRoot = NewType('BlockRoot', HexStr) SlotNumber = NewType('SlotNumber', int) +StakingModuleId = NewType('StakingModuleId', int) +NodeOperatorId = NewType('NodeOperatorId', int) +NodeOperatorGlobalIndex = tuple[StakingModuleId, NodeOperatorId] + BlockHash = NewType('BlockHash', HexStr) BlockNumber = NewType('BlockNumber', int) diff --git a/src/web3py/extensions/contracts.py b/src/web3py/extensions/contracts.py index a577eb6e8..ac48599eb 100644 --- a/src/web3py/extensions/contracts.py +++ b/src/web3py/extensions/contracts.py @@ -1,6 +1,6 @@ -import json import logging from time import sleep +from typing import cast from web3 import Web3 from web3.contract import Contract @@ -10,24 +10,34 @@ from src import variables from src.metrics.prometheus.business import FRAME_PREV_REPORT_REF_SLOT +from src.providers.execution.contracts.accounting_oracle import AccountingOracleContract +from src.providers.execution.contracts.burner import BurnerContract +from src.providers.execution.contracts.exit_bus_oracle import ExitBusOracleContract +from src.providers.execution.contracts.lido import LidoContract +from src.providers.execution.contracts.lido_locator import LidoLocatorContract +from src.providers.execution.contracts.oracle_daemon_config import OracleDaemonConfigContract +from src.providers.execution.contracts.oracle_report_sanity_checker import OracleReportSanityCheckerContract +from src.providers.execution.contracts.staking_router import StakingRouterContract +from src.providers.execution.contracts.withdrawal_queue_nft import WithdrawalQueueNftContract from src.typings import BlockStamp, SlotNumber from src.utils.cache import global_lru_cache as lru_cache + logger = logging.getLogger() class LidoContracts(Module): w3: Web3 - lido_locator: Contract - lido: Contract - accounting_oracle: Contract - staking_router: Contract - validators_exit_bus_oracle: Contract - withdrawal_queue_nft: Contract - oracle_report_sanity_checker: Contract - oracle_daemon_config: Contract - burner: Contract + lido_locator: LidoLocatorContract + lido: LidoContract + accounting_oracle: AccountingOracleContract + staking_router: StakingRouterContract + validators_exit_bus_oracle: ExitBusOracleContract + withdrawal_queue_nft: WithdrawalQueueNftContract + oracle_report_sanity_checker: OracleReportSanityCheckerContract + oracle_daemon_config: OracleDaemonConfigContract + burner: BurnerContract def __init__(self, w3: Web3): super().__init__(w3) @@ -49,8 +59,8 @@ def has_contract_address_changed(self) -> bool: def _check_contracts(self): """This is startup check that checks that contract are deployed and has valid implementation""" try: - self.accounting_oracle.functions.getContractVersion().call() - self.validators_exit_bus_oracle.functions.getContractVersion().call() + self.accounting_oracle.get_contract_version() + self.validators_exit_bus_oracle.get_contract_version() except BadFunctionCallOutput: logger.info({ 'msg': 'getContractVersion method from accounting_oracle and validators_exit_bus_oracle ' @@ -63,68 +73,62 @@ def _check_contracts(self): return def _load_contracts(self): - # Contract that stores all lido contract addresses - self.lido_locator = self.w3.eth.contract( + self.lido_locator: LidoLocatorContract = cast(LidoLocatorContract, self.w3.eth.contract( address=variables.LIDO_LOCATOR_ADDRESS, - abi=self.load_abi('LidoLocator'), + ContractFactoryClass=LidoLocatorContract, decode_tuples=True, - ) + )) - self.lido = self.w3.eth.contract( - address=self.lido_locator.functions.lido().call(), - abi=self.load_abi('Lido'), + self.lido: LidoContract = cast(LidoContract, self.w3.eth.contract( + address=self.lido_locator.lido(), + ContractFactoryClass=LidoContract, decode_tuples=True, - ) + )) - self.accounting_oracle = self.w3.eth.contract( - address=self.lido_locator.functions.accountingOracle().call(), - abi=self.load_abi('AccountingOracle'), + self.accounting_oracle: AccountingOracleContract = cast(AccountingOracleContract, self.w3.eth.contract( + address=self.lido_locator.accounting_oracle(), + ContractFactoryClass=AccountingOracleContract, decode_tuples=True, - ) + )) - self.staking_router = self.w3.eth.contract( - address=self.lido_locator.functions.stakingRouter().call(), - abi=self.load_abi('StakingRouter'), + self.staking_router: StakingRouterContract = cast(StakingRouterContract, self.w3.eth.contract( + address=self.lido_locator.staking_router(), + ContractFactoryClass=StakingRouterContract, decode_tuples=True, - ) + )) - self.validators_exit_bus_oracle = self.w3.eth.contract( - address=self.lido_locator.functions.validatorsExitBusOracle().call(), - abi=self.load_abi('ValidatorsExitBusOracle'), + self.validators_exit_bus_oracle: ExitBusOracleContract = cast(ExitBusOracleContract, self.w3.eth.contract( + address=self.lido_locator.validator_exit_bus_oracle(), + ContractFactoryClass=ExitBusOracleContract, decode_tuples=True, - ) + )) - self.withdrawal_queue_nft = self.w3.eth.contract( - address=self.lido_locator.functions.withdrawalQueue().call(), - abi=self.load_abi('WithdrawalQueueERC721'), + self.withdrawal_queue_nft: WithdrawalQueueNftContract = cast(WithdrawalQueueNftContract, self.w3.eth.contract( + address=self.lido_locator.withdrawal_queue(), + ContractFactoryClass=WithdrawalQueueNftContract, decode_tuples=True, - ) + )) - self.oracle_report_sanity_checker = self.w3.eth.contract( - address=self.lido_locator.functions.oracleReportSanityChecker().call(), - abi=self.load_abi('OracleReportSanityChecker'), + self.oracle_report_sanity_checker: OracleReportSanityCheckerContract = cast(OracleReportSanityCheckerContract, self.w3.eth.contract( + address=self.lido_locator.oracle_report_sanity_checker(), + ContractFactoryClass=OracleReportSanityCheckerContract, decode_tuples=True, - ) + )) - self.oracle_daemon_config = self.w3.eth.contract( - address=self.lido_locator.functions.oracleDaemonConfig().call(), - abi=self.load_abi('OracleDaemonConfig'), + self.oracle_daemon_config: OracleDaemonConfigContract = cast(OracleDaemonConfigContract, self.w3.eth.contract( + address=self.lido_locator.oracle_daemon_config(), + ContractFactoryClass=OracleDaemonConfigContract, decode_tuples=True, - ) + )) - self.burner = self.w3.eth.contract( - address=self.lido_locator.functions.burner().call(), - abi=self.load_abi('Burner'), + self.burner: BurnerContract = cast(BurnerContract, self.w3.eth.contract( + address=self.lido_locator.burner(), + ContractFactoryClass=BurnerContract, decode_tuples=True, - ) + )) self._check_contracts() - @staticmethod - def load_abi(abi_name: str, abi_path: str = './assets/'): - with open(f'{abi_path}{abi_name}.json') as f: - return json.load(f) - # --- Contract methods --- @lru_cache(maxsize=1) def get_withdrawal_balance(self, blockstamp: BlockStamp) -> Wei: @@ -132,32 +136,24 @@ def get_withdrawal_balance(self, blockstamp: BlockStamp) -> Wei: def get_withdrawal_balance_no_cache(self, blockstamp: BlockStamp) -> Wei: return Wei(self.w3.eth.get_balance( - self.lido_locator.functions.withdrawalVault().call( - block_identifier=blockstamp.block_hash - ), + self.lido_locator.withdrawal_vault(blockstamp.block_hash), block_identifier=blockstamp.block_hash, )) @lru_cache(maxsize=1) def get_el_vault_balance(self, blockstamp: BlockStamp) -> Wei: return Wei(self.w3.eth.get_balance( - self.lido_locator.functions.elRewardsVault().call( - block_identifier=blockstamp.block_hash - ), + self.lido_locator.el_rewards_vault(blockstamp.block_hash), block_identifier=blockstamp.block_hash, )) @lru_cache(maxsize=1) def get_accounting_last_processing_ref_slot(self, blockstamp: BlockStamp) -> SlotNumber: - result = self.accounting_oracle.functions.getLastProcessingRefSlot().call(block_identifier=blockstamp.block_hash) - logger.info({'msg': f'Accounting last processing ref slot {result}'}) + result = self.accounting_oracle.get_last_processing_ref_slot(blockstamp.block_hash) FRAME_PREV_REPORT_REF_SLOT.labels('accounting').set(result) return result def get_ejector_last_processing_ref_slot(self, blockstamp: BlockStamp) -> SlotNumber: - result = self.validators_exit_bus_oracle.functions.getLastProcessingRefSlot().call( - block_identifier=blockstamp.block_hash - ) - logger.info({'msg': f'Ejector last processing ref slot {result}'}) + result = self.validators_exit_bus_oracle.get_last_processing_ref_slot(blockstamp.block_hash) FRAME_PREV_REPORT_REF_SLOT.labels('ejector').set(result) return result diff --git a/src/web3py/extensions/lido_validators.py b/src/web3py/extensions/lido_validators.py index 24140e5a5..b84f0572d 100644 --- a/src/web3py/extensions/lido_validators.py +++ b/src/web3py/extensions/lido_validators.py @@ -1,14 +1,14 @@ import logging from dataclasses import asdict, dataclass -from typing import TYPE_CHECKING, NewType, Tuple +from typing import TYPE_CHECKING from eth_typing import ChecksumAddress from web3.module import Module from src.providers.consensus.typings import Validator from src.providers.keys.typings import LidoKey -from src.typings import BlockStamp -from src.utils.dataclass import Nested, list_of_dataclasses +from src.typings import BlockStamp, StakingModuleId, NodeOperatorId, NodeOperatorGlobalIndex +from src.utils.dataclass import Nested from src.utils.cache import global_lru_cache as lru_cache @@ -19,11 +19,6 @@ from src.web3py.typings import Web3 # pragma: no cover -StakingModuleId = NewType('StakingModuleId', int) -NodeOperatorId = NewType('NodeOperatorId', int) -NodeOperatorGlobalIndex = Tuple[StakingModuleId, NodeOperatorId] - - @dataclass class StakingModule: # unique id of the staking module @@ -175,23 +170,10 @@ def get_lido_validators_by_node_operators(self, blockstamp: BlockStamp) -> Valid def get_lido_node_operators(self, blockstamp: BlockStamp) -> list[NodeOperator]: result = [] - for module in self.get_staking_modules(blockstamp): - operators = self.w3.lido_contracts.staking_router.functions.getAllNodeOperatorDigests( - module.id - ).call(block_identifier=blockstamp.block_hash) + for module in self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash): + operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module, blockstamp.block_hash) for operator in operators: result.append(NodeOperator.from_response(operator, module)) return result - - @lru_cache(maxsize=1) - @list_of_dataclasses(StakingModule) - def get_staking_modules(self, blockstamp: BlockStamp) -> list[StakingModule]: - modules = self.w3.lido_contracts.staking_router.functions.getStakingModules().call( - block_identifier=blockstamp.block_hash, - ) - - logger.info({'msg': 'Fetch staking modules.', 'value': modules}) - - return modules diff --git a/src/web3py/typings.py b/src/web3py/typings.py index 3bccee23d..554471f16 100644 --- a/src/web3py/typings.py +++ b/src/web3py/typings.py @@ -1,3 +1,5 @@ +from typing import NewType + from web3 import Web3 as _Web3 diff --git a/tests/conftest.py b/tests/conftest.py index 4e8359422..a31f704c2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ +from dataclasses import dataclass from pathlib import Path from unittest.mock import Mock import pytest from _pytest.fixtures import FixtureRequest +from eth_typing import ChecksumAddress +from hexbytes import HexBytes from web3.middleware import construct_simple_cache_middleware from web3.types import Timestamp @@ -154,3 +157,9 @@ def get_blockstamp_by_state(w3, state_id) -> BlockStamp: ref_slot=SlotNumber(int(slot_details.message.slot)), ref_epoch=EpochNumber(int(int(slot_details.message.slot) / 12)), ) + + +@dataclass +class Account: + address: ChecksumAddress + _private_key: HexBytes diff --git a/tests/modules/accounting/test_accounting_module.py b/tests/modules/accounting/test_accounting_module.py index 3aebe0caa..c4201c64a 100644 --- a/tests/modules/accounting/test_accounting_module.py +++ b/tests/modules/accounting/test_accounting_module.py @@ -247,12 +247,12 @@ def test_submit_extra_data_non_empty( accounting.get_chain_config = Mock(return_value=chain_config) accounting.lido_validator_state_service.get_extra_data = Mock(return_value=Mock(extra_data=extra_data)) - accounting.report_contract.functions.submitReportExtraDataList = Mock() # type: ignore + accounting.report_contract.submit_report_extra_data_list = Mock() # type: ignore accounting.w3.transaction = Mock() accounting._submit_extra_data(ref_bs) - accounting.report_contract.functions.submitReportExtraDataList.assert_called_once_with(extra_data) + accounting.report_contract.submit_report_extra_data_list.assert_called_once_with(extra_data) accounting.lido_validator_state_service.get_extra_data.assert_called_once_with(ref_bs, chain_config) accounting.get_chain_config.assert_called_once_with(ref_bs) @@ -277,14 +277,14 @@ def test_submit_extra_data_empty( ): accounting.get_chain_config = Mock(return_value=chain_config) accounting.lido_validator_state_service.get_extra_data = Mock(return_value=Mock(extra_data=extra_data)) - accounting.report_contract.functions.submitReportExtraDataList = Mock() # type: ignore - accounting.report_contract.functions.submitReportExtraDataEmpty = Mock() # type: ignore + accounting.report_contract.submit_report_extra_data_list = Mock() # type: ignore + accounting.report_contract.submit_report_extra_data_empty = Mock() # type: ignore accounting.w3.transaction = Mock() accounting._submit_extra_data(ref_bs) - accounting.report_contract.functions.submitReportExtraDataEmpty.assert_called_once() - accounting.report_contract.functions.submitReportExtraDataList.assert_not_called() + accounting.report_contract.submit_report_extra_data_empty.assert_called_once() + accounting.report_contract.submit_report_extra_data_list.assert_not_called() accounting.lido_validator_state_service.get_extra_data.assert_called_once_with(ref_bs, chain_config) accounting.get_chain_config.assert_called_once_with(ref_bs) @@ -306,7 +306,7 @@ def test_can_submit_extra_data( expected: bool, bs: BlockStamp, ): - accounting._get_processing_state = Mock( + accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock( return_value=Mock( extra_data_submitted=extra_data_submitted, main_data_submitted=main_data_submitted, @@ -316,7 +316,7 @@ def test_can_submit_extra_data( out = accounting.can_submit_extra_data(bs) assert out == expected, "can_submit_extra_data returned unexpected value" - accounting._get_processing_state.assert_called_once_with(bs) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state.assert_called_once_with(bs.block_hash) @pytest.mark.unit @@ -349,15 +349,15 @@ def test_is_main_data_submitted( accounting: Accounting, bs: BlockStamp, ): - accounting._get_processing_state = Mock(return_value=Mock(main_data_submitted=False)) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock(return_value=Mock(main_data_submitted=False)) assert accounting.is_main_data_submitted(bs) is False, "is_main_data_submitted returned unexpected value" - accounting._get_processing_state.assert_called_once_with(bs) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state.assert_called_once_with(bs.block_hash) - accounting._get_processing_state.reset_mock() + accounting.w3.lido_contracts.accounting_oracle.get_processing_state.reset_mock() - accounting._get_processing_state = Mock(return_value=Mock(main_data_submitted=True)) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock(return_value=Mock(main_data_submitted=True)) assert accounting.is_main_data_submitted(bs) is True, "is_main_data_submitted returned unexpected value" - accounting._get_processing_state.assert_called_once_with(bs) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state.assert_called_once_with(bs.block_hash) @pytest.mark.unit @@ -386,23 +386,15 @@ def test_get_shares_to_burn( bs: BlockStamp, monkeypatch: pytest.MonkeyPatch, ): - call_mock = accounting.w3.lido_contracts.burner.functions.getSharesRequestedToBurn = Mock() # type: ignore + shares_data = Mock(cover_shares=42, non_cover_shares=17) + call_mock = accounting.w3.lido_contracts.burner.get_shares_requested_to_burn = Mock(return_value=shares_data) - with monkeypatch.context() as m: - shares_data = Mock(cover_shares=42, non_cover_shares=17) - m.setattr(accounting_module, 'named_tuple_to_dataclass', Mock(return_value=shares_data)) + out = accounting.get_shares_to_burn(bs) - out = accounting.get_shares_to_burn(bs) - - assert ( - out == shares_data.cover_shares + shares_data.non_cover_shares - ), "get_shares_to_burn returned unexpected value" - call_mock.assert_called_once() - - # @lru_cache - call_mock.reset_mock() - accounting.get_shares_to_burn(bs) - call_mock.assert_not_called() + assert ( + out == shares_data.cover_shares + shares_data.non_cover_shares + ), "get_shares_to_burn returned unexpected value" + call_mock.assert_called_once() @pytest.mark.unit @@ -435,7 +427,6 @@ def test_simulate_rebase_after_report( chain_config: ChainConfig, ): # NOTE: we don't test the actual rebase calculation here, just the logic of the method - accounting.get_chain_config = Mock(return_value=chain_config) accounting.w3.lido_contracts.get_withdrawal_balance = Mock(return_value=17) accounting.get_shares_to_burn = Mock(return_value=13) @@ -443,14 +434,7 @@ def test_simulate_rebase_after_report( accounting._get_consensus_lido_state = Mock(return_value=(0, 0)) accounting._get_slots_elapsed_from_last_report = Mock(return_value=42) - simulation_tx = Mock( - call=Mock( - return_value=asdict( - LidoReportRebaseFactory.build(), - ).values(), - ) - ) - accounting.w3.lido_contracts.lido.functions.handleOracleReport = Mock(return_value=simulation_tx) # type: ignore + accounting.w3.lido_contracts.lido.handle_oracle_report = Mock(return_value=LidoReportRebaseFactory.build()) # type: ignore out = accounting.simulate_rebase_after_report(ref_bs, Wei(0)) assert isinstance(out, LidoReportRebase), "simulate_rebase_after_report returned unexpected value" @@ -472,31 +456,6 @@ def test_get_newly_exited_validators_by_modules(accounting: Accounting, ref_bs: accounting.lido_validator_state_service.get_exited_lido_validators.assert_called_once_with(ref_bs) -@pytest.mark.unit -def test_get_processing_state( - accounting: Accounting, - bs: BlockStamp, - monkeypatch: pytest.MonkeyPatch, -): - processing_state = Mock() - RESULT = object() - - call_mock = accounting.report_contract.functions.getProcessingState = Mock(return_value=processing_state) # type: ignore - - with monkeypatch.context() as m: - m.setattr(accounting_module, 'named_tuple_to_dataclass', Mock(return_value=RESULT)) - - out = accounting._get_processing_state(bs) - - assert out is RESULT, "_get_processing_state returned unexpected value" - call_mock.assert_called_once() - - # @lru_cache - call_mock.reset_mock() - accounting._get_processing_state(bs) - call_mock.assert_not_called() - - @pytest.mark.unit def test_is_bunker( accounting: Accounting, diff --git a/tests/modules/accounting/test_safe_border_integration.py b/tests/modules/accounting/test_safe_border_integration.py index b7bb18621..437d1fc65 100644 --- a/tests/modules/accounting/test_safe_border_integration.py +++ b/tests/modules/accounting/test_safe_border_integration.py @@ -1,4 +1,4 @@ -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, patch, Mock import pytest @@ -31,18 +31,15 @@ def subject( lido_validators, finalization_max_negative_rebase_epoch_shift, ): - with patch.object( - SafeBorder, - '_fetch_oracle_report_limits_list', - return_value=OracleReportLimitsFactory.build(request_timestamp_margin=8 * 12 * 32), - ), patch.object( - SafeBorder, - '_fetch_finalization_max_negative_rebase_epoch_shift', - return_value=finalization_max_negative_rebase_epoch_shift, - ): - safe_border = SafeBorder(web3, past_blockstamp, ChainConfigFactory.build(), FrameConfigFactory.build()) - - return safe_border + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock( + return_value=OracleReportLimitsFactory.build(request_timestamp_margin=8 * 12 * 32) + ) + + web3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift = Mock( + return_value=finalization_max_negative_rebase_epoch_shift + ) + + return SafeBorder(web3, past_blockstamp, ChainConfigFactory.build(), FrameConfigFactory.build()) @pytest.mark.integration diff --git a/tests/modules/accounting/test_safe_border_unit.py b/tests/modules/accounting/test_safe_border_unit.py index dfd64c85c..8988de279 100644 --- a/tests/modules/accounting/test_safe_border_unit.py +++ b/tests/modules/accounting/test_safe_border_unit.py @@ -7,6 +7,7 @@ from src.providers.consensus.typings import ValidatorState from src.modules.submodules.consensus import ChainConfig, FrameConfig from tests.factory.blockstamp import ReferenceBlockStampFactory +from tests.factory.configs import OracleReportLimitsFactory FAR_FUTURE_EPOCH = 2**64 - 1 MIN_VALIDATOR_WITHDRAWABILITY_DELAY = 2**8 @@ -22,12 +23,12 @@ class WithdrawalStatus: @pytest.fixture() def chain_config(): - return ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0) + yield ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0) @pytest.fixture() def frame_config(): - return FrameConfig(initial_epoch=0, epochs_per_frame=10, fast_lane_length_slots=0) + yield FrameConfig(initial_epoch=0, epochs_per_frame=10, fast_lane_length_slots=0) @pytest.fixture() @@ -36,88 +37,98 @@ def past_blockstamp(): @pytest.fixture() -def subject( - chain_config, frame_config, past_blockstamp, web3, contracts, keys_api_client, consensus_client, lido_validators +def safe_border( + chain_config, + frame_config, + past_blockstamp, + web3, + contracts, + keys_api_client, + consensus_client, + lido_validators, ): + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock(return_value=OracleReportLimitsFactory.build()) + web3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift = Mock(return_value=100) return SafeBorder(web3, past_blockstamp, chain_config, frame_config) -def test_get_new_requests_border_epoch(subject, past_blockstamp): - border = subject._get_default_requests_border_epoch() +def test_get_new_requests_border_epoch(safe_border, past_blockstamp): + border = safe_border._get_default_requests_border_epoch() - assert border == past_blockstamp.ref_slot // SLOTS_PER_EPOCH - subject.finalization_default_shift + assert border == past_blockstamp.ref_slot // SLOTS_PER_EPOCH - safe_border.finalization_default_shift -def test_calc_validator_slashed_epoch_from_state(subject): +def test_calc_validator_slashed_epoch_from_state(safe_border): exit_epoch = 504800 withdrawable_epoch = exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + 1 validator = create_validator_stub(exit_epoch, withdrawable_epoch) - assert subject._predict_earliest_slashed_epoch(validator) == withdrawable_epoch - EPOCHS_PER_SLASHINGS_VECTOR + assert safe_border._predict_earliest_slashed_epoch(validator) == withdrawable_epoch - EPOCHS_PER_SLASHINGS_VECTOR -def test_calc_validator_slashed_epoch_from_state_undetectable(subject): +def test_calc_validator_slashed_epoch_from_state_undetectable(safe_border): exit_epoch = 504800 withdrawable_epoch = exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY validator = create_validator_stub(exit_epoch, withdrawable_epoch) - assert subject._predict_earliest_slashed_epoch(validator) is None + assert safe_border._predict_earliest_slashed_epoch(validator) is None -def test_filter_validators_with_earliest_exit_epoch(subject): +def test_filter_validators_with_earliest_exit_epoch(safe_border): validators = [ create_validator_stub(100, 105), create_validator_stub(102, 107), create_validator_stub(103, 108), ] - assert subject._filter_validators_with_earliest_exit_epoch(validators) == [validators[0]] + assert safe_border._filter_validators_with_earliest_exit_epoch(validators) == [validators[0]] -def test_get_negative_rebase_border_epoch(subject, past_blockstamp): +def test_get_negative_rebase_border_epoch(safe_border, past_blockstamp): ref_epoch = past_blockstamp.ref_slot // SLOTS_PER_EPOCH - subject._get_bunker_start_or_last_successful_report_epoch = Mock(return_value=ref_epoch) + safe_border._get_bunker_start_or_last_successful_report_epoch = Mock(return_value=ref_epoch) - assert subject._get_negative_rebase_border_epoch() == ref_epoch - subject.finalization_default_shift + assert safe_border._get_negative_rebase_border_epoch() == ref_epoch - safe_border.finalization_default_shift -def test_get_negative_rebase_border_epoch_bunker_not_started_yet(subject, past_blockstamp): +def test_get_negative_rebase_border_epoch_bunker_not_started_yet(safe_border, past_blockstamp): ref_epoch = past_blockstamp.ref_slot // SLOTS_PER_EPOCH - subject._get_bunker_start_or_last_successful_report_epoch = Mock(return_value=ref_epoch) + safe_border._get_bunker_start_or_last_successful_report_epoch = Mock(return_value=ref_epoch) - assert subject._get_negative_rebase_border_epoch() == ref_epoch - subject.finalization_default_shift + assert safe_border._get_negative_rebase_border_epoch() == ref_epoch - safe_border.finalization_default_shift -def test_get_negative_rebase_border_epoch_max(subject, past_blockstamp): +def test_get_negative_rebase_border_epoch_max(safe_border, past_blockstamp): ref_epoch = past_blockstamp.ref_slot // SLOTS_PER_EPOCH - test_epoch = ref_epoch - subject.finalization_max_negative_rebase_shift - 1 - subject._get_bunker_mode_start_timestamp = Mock(return_value=test_epoch * SLOTS_PER_EPOCH * SLOT_TIME) + max_negative_rebase_shift = safe_border.w3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift() + test_epoch = ref_epoch - max_negative_rebase_shift - 1 + safe_border._get_bunker_mode_start_timestamp = Mock(return_value=test_epoch * SLOTS_PER_EPOCH * SLOT_TIME) - assert subject._get_negative_rebase_border_epoch() == ref_epoch - subject.finalization_max_negative_rebase_shift + assert safe_border._get_negative_rebase_border_epoch() == ref_epoch - max_negative_rebase_shift -def test_get_associated_slashings_border_epoch(subject, past_blockstamp): +def test_get_associated_slashings_border_epoch(safe_border, past_blockstamp): ref_epoch = past_blockstamp.ref_slot // SLOTS_PER_EPOCH - subject._get_earliest_slashed_epoch_among_incomplete_slashings = Mock(return_value=None) - assert subject._get_associated_slashings_border_epoch() == ref_epoch - subject.finalization_default_shift + safe_border._get_earliest_slashed_epoch_among_incomplete_slashings = Mock(return_value=None) + assert safe_border._get_associated_slashings_border_epoch() == ref_epoch - safe_border.finalization_default_shift test_epoch = ref_epoch - 100 - subject._get_earliest_slashed_epoch_among_incomplete_slashings = Mock(return_value=test_epoch) + safe_border._get_earliest_slashed_epoch_among_incomplete_slashings = Mock(return_value=test_epoch) assert ( - subject._get_associated_slashings_border_epoch() - == subject.round_epoch_by_frame(test_epoch) - subject.finalization_default_shift + safe_border._get_associated_slashings_border_epoch() + == safe_border.round_epoch_by_frame(test_epoch) - safe_border.finalization_default_shift ) -def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_validators(subject, past_blockstamp): - subject.w3.lido_validators.get_lido_validators = Mock(return_value=[]) +def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_validators(safe_border, past_blockstamp): + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=[]) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() is None + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() is None -def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_slashed_validators(subject, past_blockstamp): - subject.w3.lido_validators.get_lido_validators = Mock( +def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_slashed_validators(safe_border, past_blockstamp): + safe_border.w3.lido_validators.get_lido_validators = Mock( return_value=[ create_validator_stub(100, 105), create_validator_stub(102, 107), @@ -125,21 +136,21 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_slashed_valida ] ) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() is None + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() is None def test_get_earliest_slashed_epoch_among_incomplete_slashings_withdrawable_validators( - subject, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): withdrawable_epoch = past_blockstamp.ref_epoch - 10 validators = [create_validator_stub(100, withdrawable_epoch, True)] - subject.w3.lido_validators.get_lido_validators = Mock(return_value=validators) + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=validators) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() is None + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() is None def test_get_earliest_slashed_epoch_among_incomplete_slashings_unable_to_predict( - subject, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): non_withdrawable_epoch = past_blockstamp.ref_epoch + 10 validators = [ @@ -147,14 +158,14 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_unable_to_predict non_withdrawable_epoch - MIN_VALIDATOR_WITHDRAWABILITY_DELAY, non_withdrawable_epoch, True ) ] - subject.w3.lido_validators.get_lido_validators = Mock(return_value=validators) - subject._find_earliest_slashed_epoch_rounded_to_frame = Mock(return_value=1331) + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=validators) + safe_border._find_earliest_slashed_epoch_rounded_to_frame = Mock(return_value=1331) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() == 1331 + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() == 1331 def test_get_earliest_slashed_epoch_among_incomplete_slashings_all_withdrawable( - subject, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): validators = [ create_validator_stub( @@ -164,12 +175,12 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_all_withdrawable( past_blockstamp.ref_epoch - MIN_VALIDATOR_WITHDRAWABILITY_DELAY, past_blockstamp.ref_epoch - 2, True ), ] - subject.w3.lido_validators.get_lido_validators = Mock(return_value=validators) + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=validators) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() is None + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() is None -def test_get_earliest_slashed_epoch_among_incomplete_slashings_predicted(subject, past_blockstamp, lido_validators): +def test_get_earliest_slashed_epoch_among_incomplete_slashings_predicted(safe_border, past_blockstamp, lido_validators): non_withdrawable_epoch = past_blockstamp.ref_epoch + 10 validators = [ create_validator_stub( @@ -179,15 +190,15 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_predicted(subject non_withdrawable_epoch - MIN_VALIDATOR_WITHDRAWABILITY_DELAY - 2, non_withdrawable_epoch, True ), ] - subject.w3.lido_validators.get_lido_validators = Mock(return_value=validators) + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=validators) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() == ( + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() == ( non_withdrawable_epoch - EPOCHS_PER_SLASHINGS_VECTOR ) def test_get_earliest_slashed_epoch_among_incomplete_slashings_at_least_one_unpredictable_epoch( - subject, + safe_border, past_blockstamp, lido_validators, ): @@ -200,41 +211,42 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_at_least_one_unpr non_withdrawable_epoch - MIN_VALIDATOR_WITHDRAWABILITY_DELAY, non_withdrawable_epoch, True ), ] - subject.w3.lido_validators.get_lido_validators = Mock(return_value=validators) - subject._find_earliest_slashed_epoch_rounded_to_frame = Mock(return_value=1331) + safe_border.w3.lido_validators.get_lido_validators = Mock(return_value=validators) + safe_border._find_earliest_slashed_epoch_rounded_to_frame = Mock(return_value=1331) - assert subject._get_earliest_slashed_epoch_among_incomplete_slashings() == 1331 + assert safe_border._get_earliest_slashed_epoch_among_incomplete_slashings() == 1331 -def test_get_bunker_start_or_last_successful_report_epoch_no_bunker_start(subject, past_blockstamp): - subject._get_bunker_mode_start_timestamp = Mock(return_value=None) - subject.w3.lido_contracts.get_accounting_last_processing_ref_slot = Mock(return_value=past_blockstamp.ref_slot) +def test_get_bunker_start_or_last_successful_report_epoch_no_bunker_start(safe_border, past_blockstamp): + safe_border._get_bunker_mode_start_timestamp = Mock(return_value=None) + safe_border.w3.lido_contracts.get_accounting_last_processing_ref_slot = Mock(return_value=past_blockstamp.ref_slot) - assert subject._get_bunker_start_or_last_successful_report_epoch() == past_blockstamp.ref_slot // 32 + assert safe_border._get_bunker_start_or_last_successful_report_epoch() == past_blockstamp.ref_slot // 32 -def test_get_bunker_start_or_last_successful_report_epoch(subject, past_blockstamp): - subject._get_bunker_mode_start_timestamp = Mock(return_value=past_blockstamp.ref_slot * 12) +def test_get_bunker_start_or_last_successful_report_epoch(safe_border, past_blockstamp): + safe_border._get_bunker_mode_start_timestamp = Mock(return_value=past_blockstamp.ref_slot * 12) - assert subject._get_bunker_start_or_last_successful_report_epoch() == past_blockstamp.ref_slot // 32 + assert safe_border._get_bunker_start_or_last_successful_report_epoch() == past_blockstamp.ref_slot // 32 -def test_get_last_finalized_withdrawal_request_slot(subject): +def test_get_last_finalized_withdrawal_request_slot(safe_border): timestamp = 1677230000 - subject._get_last_finalized_request_id = Mock(return_value=3) - subject._get_withdrawal_request_status = Mock(return_value=WithdrawalStatus(timestamp=timestamp)) - slot = (timestamp - subject.chain_config.genesis_time) // subject.chain_config.seconds_per_slot - epoch = slot // subject.chain_config.slots_per_epoch - first_slot = epoch * subject.chain_config.slots_per_epoch + safe_border.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id = Mock(return_value=3) + safe_border.w3.lido_contracts.withdrawal_queue_nft.get_withdrawal_status = Mock(return_value=WithdrawalStatus(timestamp=timestamp)) + + slot = (timestamp - safe_border.chain_config.genesis_time) // safe_border.chain_config.seconds_per_slot + epoch = slot // safe_border.chain_config.slots_per_epoch + first_slot = epoch * safe_border.chain_config.slots_per_epoch - assert subject._get_last_finalized_withdrawal_request_slot() == first_slot + assert safe_border._get_last_finalized_withdrawal_request_slot() == first_slot -def test_get_last_finalized_withdrawal_request_slot_no_requests(subject): - subject._get_last_finalized_request_id = Mock(return_value=0) +def test_get_last_finalized_withdrawal_request_slot_no_requests(safe_border): + safe_border.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id = Mock(return_value=0) - assert subject._get_last_finalized_withdrawal_request_slot() == 0 + assert safe_border._get_last_finalized_withdrawal_request_slot() == 0 def create_validator_stub(exit_epoch, withdrawable_epoch, slashed=False): diff --git a/tests/modules/accounting/test_validator_state.py b/tests/modules/accounting/test_validator_state.py index c3d5d0686..09725e88a 100644 --- a/tests/modules/accounting/test_validator_state.py +++ b/tests/modules/accounting/test_validator_state.py @@ -9,19 +9,15 @@ from src.modules.submodules.typings import ChainConfig from src.providers.consensus.typings import Validator, ValidatorState from src.providers.keys.typings import LidoKey -from src.typings import BlockStamp +from src.typings import StakingModuleId, NodeOperatorId from src.web3py.extensions.lido_validators import ( NodeOperator, StakingModule, LidoValidatorsProvider, LidoValidator, - ValidatorsByNodeOperator, - StakingModuleId, - NodeOperatorId, ) from tests.factory.blockstamp import ReferenceBlockStampFactory - TESTING_REF_EPOCH = 100 @@ -31,106 +27,110 @@ ) -class MockValidatorsProvider(LidoValidatorsProvider): - def get_lido_validators(self, blockstamp: BlockStamp) -> list[LidoValidator]: - raise NotImplementedError - - def get_lido_validators_by_node_operators(self, blockstamp: BlockStamp) -> ValidatorsByNodeOperator: - def validator(index: int, exit_epoch: int, pubkey: HexStr, activation_epoch: int = 0): - return LidoValidator( - lido_id=LidoKey( - key=pubkey, - depositSignature="", - operatorIndex=-1, - used=True, - moduleAddress="", - ), - **asdict( - Validator( - index=str(index), - balance="0", - status="", - validator=ValidatorState( - pubkey=pubkey, - withdrawal_credentials="0x1", - effective_balance="0", - slashed=False, - activation_eligibility_epoch="0", - activation_epoch=str(activation_epoch), - exit_epoch=str(exit_epoch), - withdrawable_epoch="0", - ), - ) - ), - ) - - return { - (StakingModuleId(1), NodeOperatorId(0)): [ - validator(index=1, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x1'), # Stuck - validator(index=2, exit_epoch=30, pubkey='0x2'), - validator(index=3, exit_epoch=50, pubkey='0x3'), - validator(index=4, exit_epoch=TESTING_REF_EPOCH, pubkey='0x4'), - ], - (StakingModuleId(1), NodeOperatorId(1)): [ - validator(index=5, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x5', activation_epoch=290), # Stuck but newest - validator( - index=6, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x6', activation_epoch=282 - ), # Stuck in the same epoch - validator(index=7, exit_epoch=20, pubkey='0x7'), - validator(index=8, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x8'), - ], +@pytest.fixture +def lido_validators(web3): + web3.attach_modules( + { + 'lido_validators': LidoValidatorsProvider, } + ) - def get_staking_modules(self, blockstamp: BlockStamp) -> list[StakingModule]: - return [ - StakingModule( - id=1, - staking_module_address='0x8a1E2986E52b441058325c315f83C9D4129bDF72', - staking_module_fee=500, - treasury_fee=500, - target_share=10000, - status=0, - name='NodeOperatorsRegistry', - last_deposit_at=1676386968, - last_deposit_block=89677, - exited_validators_count=0, - ) - ] + sm = StakingModule( + id=1, + staking_module_address='0x8a1E2986E52b441058325c315f83C9D4129bDF72', + staking_module_fee=500, + treasury_fee=500, + target_share=10000, + status=0, + name='NodeOperatorsRegistry', + last_deposit_at=1676386968, + last_deposit_block=89677, + exited_validators_count=0, + ) + + web3.lido_contracts.staking_router.get_staking_modules = Mock(return_value=[sm]) - def get_lido_node_operators(self, blockstamp: BlockStamp) -> list[NodeOperator]: - def operator(id: int, total_exited_validators: int): - return NodeOperator( - id=id, + web3.lido_contracts.staking_router.get_all_node_operator_digests = Mock( + return_value=[ + NodeOperator( + id=0, is_active=True, is_target_limit_active=False, target_validators_count=0, stuck_validators_count=0, refunded_validators_count=0, stuck_penalty_end_timestamp=0, - total_exited_validators=total_exited_validators, + total_exited_validators=0, total_deposited_validators=5, depositable_validators_count=0, - staking_module=module, - ) - - module = self.get_staking_modules(blockstamp)[0] - return [operator(id=0, total_exited_validators=0), operator(id=1, total_exited_validators=1)] - - -@pytest.fixture -def lido_validators(web3): - web3.attach_modules( - { - 'lido_validators': MockValidatorsProvider, - } + staking_module=sm, + ), + NodeOperator( + id=1, + is_active=True, + is_target_limit_active=False, + target_validators_count=0, + stuck_validators_count=0, + refunded_validators_count=0, + stuck_penalty_end_timestamp=0, + total_exited_validators=1, + total_deposited_validators=5, + depositable_validators_count=0, + staking_module=sm, + ), + ] ) + def validator(index: int, exit_epoch: int, pubkey: HexStr, activation_epoch: int = 0): + return LidoValidator( + lido_id=LidoKey( + key=pubkey, + depositSignature="", + operatorIndex=-1, + used=True, + moduleAddress="", + ), + **asdict( + Validator( + index=str(index), + balance="0", + status="", + validator=ValidatorState( + pubkey=pubkey, + withdrawal_credentials="0x1", + effective_balance="0", + slashed=False, + activation_eligibility_epoch="0", + activation_epoch=str(activation_epoch), + exit_epoch=str(exit_epoch), + withdrawable_epoch="0", + ), + ) + ), + ) + + web3.lido_validators.get_lido_validators_by_node_operators = Mock(return_value={ + (StakingModuleId(1), NodeOperatorId(0)): [ + validator(index=1, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x1'), # Stuck + validator(index=2, exit_epoch=30, pubkey='0x2'), + validator(index=3, exit_epoch=50, pubkey='0x3'), + validator(index=4, exit_epoch=TESTING_REF_EPOCH, pubkey='0x4'), + ], + (StakingModuleId(1), NodeOperatorId(1)): [ + validator(index=5, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x5', activation_epoch=290), # Stuck but newest + validator( + index=6, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x6', activation_epoch=282 + ), # Stuck in the same epoch + validator(index=7, exit_epoch=20, pubkey='0x7'), + validator(index=8, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x8'), + ], + }) + @pytest.fixture def validator_state(web3, contracts, consensus_client, lido_validators): service = LidoValidatorStateService(web3) - requested_indexes = [3, 8] - service._get_last_requested_validator_indices = Mock(return_value=requested_indexes) + service.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices = Mock(return_value=[3, 8]) return service @@ -139,9 +139,10 @@ def chain_config(): return ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0) +@pytest.mark.unit def test_get_lido_new_stuck_validators(web3, validator_state, chain_config): validator_state.get_last_requested_to_exit_pubkeys = Mock(return_value={"0x8"}) - validator_state.get_validator_delinquent_timeout_in_slot = Mock(return_value=0) + validator_state.w3.lido_contracts.oracle_daemon_config.validator_delinquent_timeout_in_slots = Mock(return_value=0) stuck_validators = validator_state.get_lido_newly_stuck_validators(blockstamp, chain_config) assert stuck_validators == {(1, 0): 1} diff --git a/tests/modules/accounting/test_withdrawal_integration.py b/tests/modules/accounting/test_withdrawal_integration.py index b6e619c6b..3f1fb69e9 100644 --- a/tests/modules/accounting/test_withdrawal_integration.py +++ b/tests/modules/accounting/test_withdrawal_integration.py @@ -6,22 +6,22 @@ from tests.conftest import get_blockstamp_by_state -@pytest.fixture() +@pytest.fixture def chain_config(): return ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=1675263480) -@pytest.fixture() +@pytest.fixture def frame_config(): return FrameConfig(initial_epoch=0, epochs_per_frame=10, fast_lane_length_slots=0) -@pytest.fixture() +@pytest.fixture def past_blockstamp(web3, consensus_client): return get_blockstamp_by_state(web3, 'finalized') -@pytest.fixture() +@pytest.fixture def subject(web3, past_blockstamp, chain_config, frame_config, contracts, keys_api_client, consensus_client): return Withdrawal(web3, past_blockstamp, chain_config, frame_config) @@ -30,14 +30,8 @@ def test_happy_path(subject, past_blockstamp): withdrawal_vault_balance = subject.w3.lido_contracts.get_withdrawal_balance(past_blockstamp) el_rewards_vault_balance = subject.w3.lido_contracts.get_el_vault_balance(past_blockstamp) - expected_min_withdrawal_id = ( - subject.w3.lido_contracts.withdrawal_queue_nft.functions.getLastFinalizedRequestId().call( - block_identifier=past_blockstamp.block_hash - ) - ) - expected_max_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.functions.getLastRequestId().call( - block_identifier=past_blockstamp.block_hash - ) + expected_min_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id(past_blockstamp.block_hash) + expected_max_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_request_id(past_blockstamp.block_hash) results = subject.get_finalization_batches( False, SHARE_RATE_PRECISION_E27, withdrawal_vault_balance, el_rewards_vault_balance diff --git a/tests/modules/accounting/test_withdrawal_unit.py b/tests/modules/accounting/test_withdrawal_unit.py index a4c62aa9c..70ddfd3f4 100644 --- a/tests/modules/accounting/test_withdrawal_unit.py +++ b/tests/modules/accounting/test_withdrawal_unit.py @@ -25,7 +25,6 @@ def past_blockstamp(web3, consensus_client): @pytest.fixture() def subject(web3, past_blockstamp, chain_config, frame_config, contracts, keys_api_client, consensus_client): - # web3.lido_contracts.oracle_report_sanity_checker.functions.getOracleReportLimits().call = Mock(return_value=0) return Withdrawal(web3, past_blockstamp, chain_config, frame_config) @@ -39,7 +38,7 @@ def test_returns_empty_batch_if_there_is_no_requests(subject: Withdrawal): @pytest.mark.unit def test_returns_empty_batch_if_paused(subject: Withdrawal): - subject._is_requests_finalization_paused = Mock(return_value=True) + subject.w3.lido_contracts.withdrawal_queue_nft.is_paused = Mock(return_value=True) result = subject.get_finalization_batches(True, 100, 0, 0) assert result == [] @@ -61,7 +60,7 @@ def test_calculate_finalization_batches(subject: Withdrawal, past_blockstamp): state_initial = BatchState(remaining_eth_budget=100, finished=False, batches=[1] + [0] * 35, batches_length=1) state_final = BatchState(remaining_eth_budget=100, finished=True, batches=[2] + [0] * 35, batches_length=2) subject._fetch_finalization_batches = Mock(side_effect=[state_initial, state_final]) - subject._fetch_max_batches_length = Mock(return_value=36) + subject.w3.lido_contracts.withdrawal_queue_nft.max_batches_length = Mock(return_value=36) result = subject._calculate_finalization_batches(1, SHARE_RATE_PRECISION_E27, past_blockstamp.block_timestamp) diff --git a/tests/modules/ejector/test_data_encode.py b/tests/modules/ejector/test_data_encode.py index 5e7ff1731..2645f05d1 100644 --- a/tests/modules/ejector/test_data_encode.py +++ b/tests/modules/ejector/test_data_encode.py @@ -12,11 +12,8 @@ encode_data, sort_validators_to_eject, ) -from src.web3py.extensions.lido_validators import ( - LidoValidator, - NodeOperatorId, - StakingModuleId, -) +from src.typings import StakingModuleId, NodeOperatorId +from src.web3py.extensions.lido_validators import LidoValidator from tests.factory.no_registry import LidoValidatorFactory diff --git a/tests/modules/ejector/test_ejector.py b/tests/modules/ejector/test_ejector.py index 42d1a123f..81612c9c5 100644 --- a/tests/modules/ejector/test_ejector.py +++ b/tests/modules/ejector/test_ejector.py @@ -90,7 +90,7 @@ def test_should_not_report_on_no_withdraw_requests( ejector: Ejector, ref_blockstamp: ReferenceBlockStamp, ) -> None: - ejector.get_total_unfinalized_withdrawal_requests_amount = Mock(return_value=0) + ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth = Mock(return_value=0) result = ejector.get_validators_to_eject(ref_blockstamp) assert result == [], "Should not report on no withdraw requests" @@ -104,7 +104,7 @@ def test_no_validators_to_eject( monkeypatch: pytest.MonkeyPatch, ): ejector.get_chain_config = Mock(return_value=chain_config) - ejector.get_total_unfinalized_withdrawal_requests_amount = Mock(return_value=100) + ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth = Mock(return_value=100) ejector.prediction_service.get_rewards_per_epoch = Mock(return_value=1) ejector._get_sweep_delay_in_epochs = Mock(return_value=1) @@ -130,7 +130,7 @@ def test_simple( monkeypatch: pytest.MonkeyPatch, ): ejector.get_chain_config = Mock(return_value=chain_config) - ejector.get_total_unfinalized_withdrawal_requests_amount = Mock(return_value=200) + ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth = Mock(return_value=200) ejector.prediction_service.get_rewards_per_epoch = Mock(return_value=1) ejector._get_sweep_delay_in_epochs = Mock(return_value=ref_blockstamp.ref_epoch) ejector._get_total_el_balance = Mock(return_value=100) @@ -159,7 +159,7 @@ def test_simple( @pytest.mark.unit @pytest.mark.usefixtures("contracts") def test_get_unfinalized_steth(ejector: Ejector, blockstamp: BlockStamp) -> None: - result = ejector.get_total_unfinalized_withdrawal_requests_amount(blockstamp) + result = ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth(blockstamp) assert result == 8362187000000000000, "Unexpected unfinalized stETH" diff --git a/tests/modules/ejector/test_exit_order_iterator.py b/tests/modules/ejector/test_exit_order_iterator.py index 77bb8703c..69cf8e8b9 100644 --- a/tests/modules/ejector/test_exit_order_iterator.py +++ b/tests/modules/ejector/test_exit_order_iterator.py @@ -1,3 +1,5 @@ +from unittest.mock import Mock + import pytest from src.providers.consensus.typings import ValidatorState @@ -6,7 +8,7 @@ from src.services.exit_order_iterator_state import NodeOperatorPredictableState, ExitOrderIteratorStateService from src.web3py.extensions.lido_validators import LidoValidator, StakingModuleId, NodeOperatorId from tests.factory.blockstamp import ReferenceBlockStampFactory -from tests.factory.configs import ChainConfigFactory +from tests.factory.configs import ChainConfigFactory, OracleReportLimitsFactory from tests.factory.no_registry import LidoValidatorFactory @@ -137,9 +139,9 @@ def mock_exit_order_iterator_state_service(monkeypatch): class MockedExitOrderIteratorStateService(ExitOrderIteratorStateService): pass - inner_ = lambda _: None - inner_.max_validator_exit_requests_per_report = 100 - MockedExitOrderIteratorStateService.get_oracle_report_limits = lambda *_: inner_ + MockedExitOrderIteratorStateService.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( + Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) + ) MockedExitOrderIteratorStateService.get_operator_network_penetration_threshold = lambda *_: 0.05 MockedExitOrderIteratorStateService.get_operators_with_last_exited_validator_indexes = lambda *_: {} MockedExitOrderIteratorStateService.get_exitable_lido_validators = lambda *_: [] diff --git a/tests/modules/submodules/consensus/test_consensus.py b/tests/modules/submodules/consensus/test_consensus.py index aa6b07fb4..df0ec608b 100644 --- a/tests/modules/submodules/consensus/test_consensus.py +++ b/tests/modules/submodules/consensus/test_consensus.py @@ -4,13 +4,12 @@ import pytest from src import variables -from src.modules.accounting.typings import Account from src.modules.submodules import consensus as consensus_module from src.modules.submodules.consensus import ZERO_HASH, ConsensusModule, IsNotMemberException, MemberInfo from src.modules.submodules.typings import ChainConfig from src.providers.consensus.typings import BeaconSpecResponse from src.typings import BlockStamp, ReferenceBlockStamp -from tests.conftest import get_blockstamp_by_state +from tests.conftest import get_blockstamp_by_state, Account from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.configs import BeaconSpecResponseFactory, ChainConfigFactory diff --git a/tests/modules/submodules/consensus/test_reports.py b/tests/modules/submodules/consensus/test_reports.py index bff035970..cc67f2e37 100644 --- a/tests/modules/submodules/consensus/test_reports.py +++ b/tests/modules/submodules/consensus/test_reports.py @@ -3,8 +3,9 @@ import pytest from hexbytes import HexBytes from src import variables -from src.modules.accounting.typings import Account, ReportData +from src.modules.accounting.typings import ReportData from src.modules.submodules.typings import ChainConfig, FrameConfig, ZERO_HASH +from tests.conftest import Account from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.member_info import MemberInfoFactory diff --git a/tests/web3_extentions/test_tx_utils.py b/tests/web3_extentions/test_tx_utils.py index 7b82a1909..70329ade9 100644 --- a/tests/web3_extentions/test_tx_utils.py +++ b/tests/web3_extentions/test_tx_utils.py @@ -6,8 +6,8 @@ from src import variables from src.constants import MAX_BLOCK_GAS_LIMIT -from src.modules.accounting.typings import Account from src.utils import input +from tests.conftest import Account class Transaction: From 126c39bfe1932f2b57a690b2f1a10729779dc50b Mon Sep 17 00:00:00 2001 From: F4ever Date: Wed, 17 Apr 2024 03:01:03 +0200 Subject: [PATCH 02/21] temp changes --- src/services/safe_border.py | 2 + src/web3py/extensions/lido_validators.py | 2 +- .../accounting/test_withdrawal_unit.py | 2 + tests/services/test_safe_border.py | 62 ++++++++++--------- tests/web3_extentions/test_lido_validators.py | 3 +- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/services/safe_border.py b/src/services/safe_border.py index 9d0063739..f46f9e5b9 100644 --- a/src/services/safe_border.py +++ b/src/services/safe_border.py @@ -56,7 +56,9 @@ def __init__( self.frame_config = frame_config self.converter = Web3Converter(chain_config, frame_config) + self._retrieve_constants() + def _retrieve_constants(self): limits_list = self.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits(self.blockstamp.block_hash) self.finalization_default_shift = math.ceil( diff --git a/src/web3py/extensions/lido_validators.py b/src/web3py/extensions/lido_validators.py index b84f0572d..0e031106c 100644 --- a/src/web3py/extensions/lido_validators.py +++ b/src/web3py/extensions/lido_validators.py @@ -171,7 +171,7 @@ def get_lido_node_operators(self, blockstamp: BlockStamp) -> list[NodeOperator]: result = [] for module in self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash): - operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module, blockstamp.block_hash) + operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module.id, blockstamp.block_hash) for operator in operators: result.append(NodeOperator.from_response(operator, module)) diff --git a/tests/modules/accounting/test_withdrawal_unit.py b/tests/modules/accounting/test_withdrawal_unit.py index 70ddfd3f4..3caed23dd 100644 --- a/tests/modules/accounting/test_withdrawal_unit.py +++ b/tests/modules/accounting/test_withdrawal_unit.py @@ -6,6 +6,7 @@ from tests.conftest import get_blockstamp_by_state from src.constants import SHARE_RATE_PRECISION_E27 from src.modules.accounting.typings import BatchState +from tests.factory.configs import OracleReportLimitsFactory @pytest.fixture() @@ -25,6 +26,7 @@ def past_blockstamp(web3, consensus_client): @pytest.fixture() def subject(web3, past_blockstamp, chain_config, frame_config, contracts, keys_api_client, consensus_client): + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock(return_value=OracleReportLimitsFactory.build()) return Withdrawal(web3, past_blockstamp, chain_config, frame_config) diff --git a/tests/services/test_safe_border.py b/tests/services/test_safe_border.py index 9f5719d41..d5bbb491a 100644 --- a/tests/services/test_safe_border.py +++ b/tests/services/test_safe_border.py @@ -1,12 +1,12 @@ import pytest -from unittest.mock import MagicMock +from unittest.mock import Mock from src.services.safe_border import SafeBorder from tests.factory.no_registry import ValidatorFactory @pytest.mark.unit @pytest.mark.parametrize( - "is_bunker, negative_rebase_border_epoch, associated_slashings_border_epoch, default_requests_border_epoch, expected", + "is_bunker,negative_rebase_border_epoch,associated_slashings_border_epoch,default_requests_border_epoch,expected", [ (True, 1, 2, 7, 1), (True, 20, 5, 7, 5), @@ -20,15 +20,17 @@ def test_get_safe_border_epoch( default_requests_border_epoch, expected, ): - SafeBorder._retrieve_constants = MagicMock - SafeBorder._get_negative_rebase_border_epoch = MagicMock(return_value=negative_rebase_border_epoch) - SafeBorder._get_associated_slashings_border_epoch = MagicMock(return_value=associated_slashings_border_epoch) - SafeBorder._get_default_requests_border_epoch = MagicMock(return_value=default_requests_border_epoch) - - web3Mock = MagicMock - web3Mock.lido_contracts = MagicMock + SafeBorder._retrieve_constants = Mock() + sb = SafeBorder( + w3=Mock(), + blockstamp=Mock(), + chain_config=Mock(), + frame_config=Mock(), + ) - sb = SafeBorder(w3=web3Mock, blockstamp=MagicMock, chain_config=MagicMock, frame_config=MagicMock) + sb._get_negative_rebase_border_epoch = Mock(return_value=negative_rebase_border_epoch) + sb._get_associated_slashings_border_epoch = Mock(return_value=associated_slashings_border_epoch) + sb._get_default_requests_border_epoch = Mock(return_value=default_requests_border_epoch) actual = sb.get_safe_border_epoch(is_bunker=is_bunker) assert actual == expected @@ -45,19 +47,19 @@ def test_get_safe_border_epoch( def test_get_bunker_start_or_last_successful_report_epoch( get_bunker_timestamp, slots_per_epoch, last_processing_ref_slot, initial_epoch, expected ): - SafeBorder._retrieve_constants = MagicMock - SafeBorder._get_negative_rebase_border_epoch = MagicMock() - SafeBorder._get_associated_slashings_border_epoch = MagicMock() - SafeBorder._get_default_requests_border_epoch = MagicMock() - SafeBorder._get_bunker_mode_start_timestamp = MagicMock(return_value=get_bunker_timestamp) - web3Mock = MagicMock() - web3Mock.lido_contracts.get_accounting_last_processing_ref_slot = MagicMock(return_value=last_processing_ref_slot) - - chain_config = MagicMock + SafeBorder._retrieve_constants = Mock + SafeBorder._get_negative_rebase_border_epoch = Mock() + SafeBorder._get_associated_slashings_border_epoch = Mock() + SafeBorder._get_default_requests_border_epoch = Mock() + SafeBorder._get_bunker_mode_start_timestamp = Mock(return_value=get_bunker_timestamp) + web3Mock = Mock() + web3Mock.lido_contracts.get_accounting_last_processing_ref_slot = Mock(return_value=last_processing_ref_slot) + + chain_config = Mock chain_config.slots_per_epoch = slots_per_epoch chain_config.initial_epoch = initial_epoch - sb = SafeBorder(w3=web3Mock, blockstamp=MagicMock, chain_config=chain_config, frame_config=MagicMock) + sb = SafeBorder(w3=web3Mock, blockstamp=Mock, chain_config=chain_config, frame_config=Mock) actual = sb._get_bunker_start_or_last_successful_report_epoch() @@ -83,7 +85,7 @@ def validators(): @pytest.fixture def frame_config(): - frame_config = MagicMock + frame_config = Mock frame_config.initial_epoch = 50 frame_config.epochs_per_frame = 2 @@ -92,7 +94,7 @@ def frame_config(): @pytest.fixture def chain_config(): - chain_config = MagicMock + chain_config = Mock chain_config.slots_per_epoch = 12 return chain_config @@ -100,7 +102,7 @@ def chain_config(): @pytest.fixture def blockstamp(): - blockstamp = MagicMock + blockstamp = Mock blockstamp.ref_epoch = 99 return blockstamp @@ -123,16 +125,16 @@ def test_find_earliest_slashed_epoch_rounded_to_frame( last_finalized_withdrawal_request_slot, expected, ): - SafeBorder._retrieve_constants = MagicMock() - SafeBorder._get_negative_rebase_border_epoch = MagicMock() - SafeBorder._get_associated_slashings_border_epoch = MagicMock() - SafeBorder._get_last_finalized_withdrawal_request_slot = MagicMock( + SafeBorder._retrieve_constants = Mock() + SafeBorder._get_negative_rebase_border_epoch = Mock() + SafeBorder._get_associated_slashings_border_epoch = Mock() + SafeBorder._get_last_finalized_withdrawal_request_slot = Mock( return_value=last_finalized_withdrawal_request_slot ) - SafeBorder._slashings_in_frame = MagicMock(return_value=slashings_in_frame) + SafeBorder._slashings_in_frame = Mock(return_value=slashings_in_frame) - web3Mock = MagicMock() - web3Mock.lido_contracts = MagicMock() + web3Mock = Mock() + web3Mock.lido_contracts = Mock() sb = SafeBorder(w3=web3Mock, blockstamp=blockstamp, chain_config=chain_config, frame_config=frame_config) diff --git a/tests/web3_extentions/test_lido_validators.py b/tests/web3_extentions/test_lido_validators.py index be3523683..5af2efa75 100644 --- a/tests/web3_extentions/test_lido_validators.py +++ b/tests/web3_extentions/test_lido_validators.py @@ -2,7 +2,7 @@ import pytest -from src.web3py.extensions.lido_validators import CountOfKeysDiffersException, LidoValidatorsProvider +from src.web3py.extensions.lido_validators import CountOfKeysDiffersException from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.no_registry import ( LidoKeyFactory, @@ -12,6 +12,7 @@ ValidatorFactory, ) + blockstamp = ReferenceBlockStampFactory.build() From 84539bb0628adc396cb4bb72aad4e8e56f435017 Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 00:55:21 +0200 Subject: [PATCH 03/21] rename typings --- src/main.py | 4 +-- src/modules/accounting/accounting.py | 6 ++--- src/modules/accounting/extra_data.py | 6 ++--- .../accounting/{typings.py => types.py} | 6 ++--- src/modules/checks/suites/conftest.py | 4 +-- src/modules/checks/suites/consensus_node.py | 2 +- src/modules/ejector/ejector.py | 8 +++--- src/modules/ejector/{typings.py => types.py} | 2 +- src/modules/submodules/consensus.py | 6 ++--- src/modules/submodules/oracle_module.py | 4 +-- .../submodules/{typings.py => types.py} | 2 +- src/providers/consensus/client.py | 4 +-- .../consensus/{typings.py => types.py} | 2 +- src/providers/execution/base_interface.py | 3 ++- .../execution/contracts/accounting_oracle.py | 2 +- .../execution/contracts/base_oracle.py | 3 ++- src/providers/execution/contracts/burner.py | 2 +- .../execution/contracts/exit_bus_oracle.py | 2 +- .../execution/contracts/hash_consensus.py | 2 +- src/providers/execution/contracts/lido.py | 2 +- .../contracts/oracle_report_sanity_checker.py | 2 +- .../execution/contracts/staking_router.py | 1 + .../contracts/withdrawal_queue_nft.py | 4 +-- src/providers/keys/client.py | 4 +-- src/providers/keys/{typings.py => types.py} | 0 src/services/bunker.py | 8 +++--- .../bunker_cases/abnormal_cl_rebase.py | 12 ++++----- .../bunker_cases/midterm_slashing_penalty.py | 6 ++--- .../bunker_cases/{typings.py => types.py} | 0 src/services/exit_order_iterator.py | 6 ++--- src/services/exit_order_iterator_state.py | 14 +++++----- src/services/prediction.py | 6 ++--- src/services/safe_border.py | 6 ++--- src/services/validator_state.py | 8 +++--- src/services/withdrawal.py | 10 +++---- src/{typings.py => types.py} | 0 src/utils/blockstamp.py | 4 +-- src/utils/events.py | 2 +- src/utils/slot.py | 4 +-- src/utils/validator_state.py | 4 +-- src/utils/web3converter.py | 4 +-- src/web3py/extensions/contracts.py | 2 +- src/web3py/extensions/lido_validators.py | 12 ++++----- src/web3py/{typings.py => types.py} | 0 tests/conftest.py | 6 ++--- tests/factory/blockstamp.py | 2 +- tests/factory/configs.py | 8 +++--- tests/factory/contract_responses.py | 2 +- tests/factory/member_info.py | 2 +- tests/factory/no_registry.py | 4 +-- tests/modules/accounting/bunker/conftest.py | 22 ++++----------- .../modules/accounting/bunker/test_bunker.py | 8 +++--- .../bunker/test_bunker_abnormal_cl_rebase.py | 4 +-- .../bunker/test_bunker_medterm_penalty.py | 6 ++--- .../accounting/test_accounting_module.py | 6 ++--- .../test_safe_border_integration.py | 2 +- .../accounting/test_safe_border_unit.py | 2 +- .../accounting/test_validator_state.py | 8 +++--- .../accounting/test_withdrawal_integration.py | 2 +- .../accounting/test_withdrawal_unit.py | 4 +-- tests/modules/ejector/test_data_encode.py | 2 +- tests/modules/ejector/test_ejector.py | 27 +++++++------------ .../ejector/test_exit_order_iterator.py | 15 +++++++---- .../ejector/test_exit_order_state_service.py | 4 +-- tests/modules/ejector/test_prediction.py | 14 +++++----- .../modules/submodules/consensus/conftest.py | 2 +- .../submodules/consensus/test_consensus.py | 6 ++--- .../submodules/consensus/test_reports.py | 4 +-- .../modules/submodules/test_oracle_module.py | 2 +- .../test_consensus_client.py | 4 +-- tests/utils/test_events.py | 2 +- tests/utils/test_validator_state_utils.py | 4 +-- tests/utils/test_web3_converter.py | 4 +-- 73 files changed, 178 insertions(+), 191 deletions(-) rename src/modules/accounting/{typings.py => types.py} (96%) rename src/modules/ejector/{typings.py => types.py} (94%) rename src/modules/submodules/{typings.py => types.py} (95%) rename src/providers/consensus/{typings.py => types.py} (98%) rename src/providers/keys/{typings.py => types.py} (100%) rename src/services/bunker_cases/{typings.py => types.py} (100%) rename src/{typings.py => types.py} (100%) rename src/web3py/{typings.py => types.py} (100%) diff --git a/src/main.py b/src/main.py index 01be0bcbb..c499edb4f 100644 --- a/src/main.py +++ b/src/main.py @@ -11,7 +11,7 @@ from src.modules.accounting.accounting import Accounting from src.modules.ejector.ejector import Ejector from src.modules.checks.checks_module import ChecksModule -from src.typings import OracleModule +from src.types import OracleModule from src.utils.build import get_build_info from src.web3py.extensions import ( LidoContracts, @@ -22,7 +22,7 @@ FallbackProviderModule ) from src.web3py.middleware import metrics_collector -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from src.web3py.contract_tweak import tweak_w3_contracts diff --git a/src/modules/accounting/accounting.py b/src/modules/accounting/accounting.py index dd4b4ddbb..ff1c9e45e 100644 --- a/src/modules/accounting/accounting.py +++ b/src/modules/accounting/accounting.py @@ -6,7 +6,7 @@ from src import variables from src.constants import SHARE_RATE_PRECISION_E27 -from src.modules.accounting.typings import ( +from src.modules.accounting.types import ( ReportData, LidoReportRebase, ) @@ -23,10 +23,10 @@ from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay from src.services.withdrawal import Withdrawal from src.services.bunker import BunkerService -from src.typings import BlockStamp, Gwei, ReferenceBlockStamp, StakingModuleId, NodeOperatorGlobalIndex +from src.types import BlockStamp, Gwei, ReferenceBlockStamp, StakingModuleId, NodeOperatorGlobalIndex from src.utils.cache import global_lru_cache as lru_cache from src.variables import ALLOW_REPORTING_IN_BUNKER_MODE -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from src.web3py.extensions.lido_validators import StakingModule diff --git a/src/modules/accounting/extra_data.py b/src/modules/accounting/extra_data.py index c4e011701..368c1261e 100644 --- a/src/modules/accounting/extra_data.py +++ b/src/modules/accounting/extra_data.py @@ -4,9 +4,9 @@ from hexbytes import HexBytes -from src.modules.submodules.typings import ZERO_HASH -from src.typings import NodeOperatorGlobalIndex -from src.web3py.typings import Web3 +from src.modules.submodules.types import ZERO_HASH +from src.types import NodeOperatorGlobalIndex +from src.web3py.types import Web3 class ItemType(Enum): diff --git a/src/modules/accounting/typings.py b/src/modules/accounting/types.py similarity index 96% rename from src/modules/accounting/typings.py rename to src/modules/accounting/types.py index d9fc6a5cb..b2036d805 100644 --- a/src/modules/accounting/typings.py +++ b/src/modules/accounting/types.py @@ -4,7 +4,7 @@ from hexbytes import HexBytes from web3.types import Wei -from src.typings import SlotNumber, Gwei, StakingModuleId +from src.types import SlotNumber, Gwei, StakingModuleId @dataclass @@ -84,7 +84,7 @@ class LidoReportRebase: class BatchState: remaining_eth_budget: int finished: bool - batches: list[int] + batches: tuple[int, ...] batches_length: int def as_tuple(self): @@ -92,7 +92,7 @@ def as_tuple(self): self.remaining_eth_budget, self.finished, self.batches, - self.batches_length + self.batches_length, ) diff --git a/src/modules/checks/suites/conftest.py b/src/modules/checks/suites/conftest.py index 9e31852f3..a9e1b84cc 100644 --- a/src/modules/checks/suites/conftest.py +++ b/src/modules/checks/suites/conftest.py @@ -4,7 +4,7 @@ from xdist.dsession import TerminalDistReporter # type: ignore[import] from src import variables -from src.typings import EpochNumber, SlotNumber, BlockRoot +from src.types import EpochNumber, SlotNumber, BlockRoot from src.utils.blockstamp import build_blockstamp from src.utils.slot import get_reference_blockstamp from src.web3py.contract_tweak import tweak_w3_contracts @@ -16,7 +16,7 @@ LidoContracts, FallbackProviderModule, ) -from src.web3py.typings import Web3 +from src.web3py.types import Web3 TITLE_PROPERTY_NAME = "test_title" diff --git a/src/modules/checks/suites/consensus_node.py b/src/modules/checks/suites/consensus_node.py index ab351bba7..e85e717bd 100644 --- a/src/modules/checks/suites/consensus_node.py +++ b/src/modules/checks/suites/consensus_node.py @@ -1,5 +1,5 @@ """Consensus node""" -from src.web3py.typings import Web3 +from src.web3py.types import Web3 def check_validators_provided(web3: Web3, blockstamp): diff --git a/src/modules/ejector/ejector.py b/src/modules/ejector/ejector.py index 6ea17f3ed..d4ad01362 100644 --- a/src/modules/ejector/ejector.py +++ b/src/modules/ejector/ejector.py @@ -20,15 +20,15 @@ ) from src.metrics.prometheus.duration_meter import duration_meter from src.modules.ejector.data_encode import encode_data -from src.modules.ejector.typings import EjectorProcessingState, ReportData +from src.modules.ejector.types import EjectorProcessingState, ReportData from src.modules.submodules.consensus import ConsensusModule from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay -from src.providers.consensus.typings import Validator +from src.providers.consensus.types import Validator from src.providers.execution.contracts.exit_bus_oracle import ExitBusOracleContract from src.services.exit_order_iterator import ExitOrderIterator from src.services.prediction import RewardsPredictionService from src.services.validator_state import LidoValidatorStateService -from src.typings import BlockStamp, EpochNumber, ReferenceBlockStamp, NodeOperatorGlobalIndex +from src.types import BlockStamp, EpochNumber, ReferenceBlockStamp, NodeOperatorGlobalIndex from src.utils.cache import global_lru_cache as lru_cache from src.utils.validator_state import ( is_active_validator, @@ -36,7 +36,7 @@ is_partially_withdrawable_validator, ) from src.web3py.extensions.lido_validators import LidoValidator -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/modules/ejector/typings.py b/src/modules/ejector/types.py similarity index 94% rename from src/modules/ejector/typings.py rename to src/modules/ejector/types.py index fa5c9f9ff..c60268e0c 100644 --- a/src/modules/ejector/typings.py +++ b/src/modules/ejector/types.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from src.typings import SlotNumber +from src.types import SlotNumber @dataclass diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index 54fa7ee1b..85a5f45c1 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -12,7 +12,7 @@ from src.metrics.prometheus.basic import ORACLE_SLOT_NUMBER, ORACLE_BLOCK_NUMBER, GENESIS_TIME, ACCOUNT_BALANCE from src.providers.execution.contracts.base_oracle import BaseOracleContract from src.providers.execution.contracts.hash_consensus import HashConsensusContract -from src.typings import BlockStamp, ReferenceBlockStamp, SlotNumber +from src.types import BlockStamp, ReferenceBlockStamp, SlotNumber from src.metrics.prometheus.business import ( ORACLE_MEMBER_LAST_REPORT_REF_SLOT, FRAME_CURRENT_REF_SLOT, @@ -20,13 +20,13 @@ ORACLE_MEMBER_INFO ) from src.modules.submodules.exceptions import IsNotMemberException, IncompatibleContractVersion -from src.modules.submodules.typings import ChainConfig, MemberInfo, ZERO_HASH, CurrentFrame, FrameConfig +from src.modules.submodules.types import ChainConfig, MemberInfo, ZERO_HASH, CurrentFrame, FrameConfig from src.utils.abi import named_tuple_to_dataclass from src.utils.blockstamp import build_blockstamp from src.utils.web3converter import Web3Converter from src.utils.slot import get_reference_blockstamp from src.utils.cache import global_lru_cache as lru_cache -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/modules/submodules/oracle_module.py b/src/modules/submodules/oracle_module.py index 546202ef2..6823eb594 100644 --- a/src/modules/submodules/oracle_module.py +++ b/src/modules/submodules/oracle_module.py @@ -16,11 +16,11 @@ from src.web3py.extensions.lido_validators import CountOfKeysDiffersException from src.utils.blockstamp import build_blockstamp from src.utils.slot import NoSlotsAvailable, SlotNotFinalized, InconsistentData -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from web3_multi_provider import NoActiveProviderError from src import variables -from src.typings import SlotNumber, BlockStamp, BlockRoot +from src.types import SlotNumber, BlockStamp, BlockRoot logger = logging.getLogger(__name__) diff --git a/src/modules/submodules/typings.py b/src/modules/submodules/types.py similarity index 95% rename from src/modules/submodules/typings.py rename to src/modules/submodules/types.py index b52fe9d3c..9ae3195fd 100644 --- a/src/modules/submodules/typings.py +++ b/src/modules/submodules/types.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from src.typings import SlotNumber +from src.types import SlotNumber ZERO_HASH = bytes([0]*32) diff --git a/src/providers/consensus/client.py b/src/providers/consensus/client.py index adcb8a456..e92969176 100644 --- a/src/providers/consensus/client.py +++ b/src/providers/consensus/client.py @@ -3,7 +3,7 @@ from src.metrics.logging import logging from src.metrics.prometheus.basic import CL_REQUESTS_DURATION -from src.providers.consensus.typings import ( +from src.providers.consensus.types import ( BlockDetailsResponse, BlockHeaderFullResponse, BlockHeaderResponseData, @@ -13,7 +13,7 @@ GenesisResponse, ) from src.providers.http_provider import HTTPProvider, NotOkResponse -from src.typings import BlockRoot, BlockStamp, SlotNumber +from src.types import BlockRoot, BlockStamp, SlotNumber from src.utils.dataclass import list_of_dataclasses from src.utils.cache import global_lru_cache as lru_cache diff --git a/src/providers/consensus/typings.py b/src/providers/consensus/types.py similarity index 98% rename from src/providers/consensus/typings.py rename to src/providers/consensus/types.py index f5022e56b..b38e4462f 100644 --- a/src/providers/consensus/typings.py +++ b/src/providers/consensus/types.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional -from src.typings import BlockRoot, StateRoot +from src.types import BlockRoot, StateRoot from src.utils.dataclass import Nested, FromResponse diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index a2bd6f08f..5c0a9ba05 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -2,7 +2,8 @@ from typing import Optional, Any from web3 import Web3 -from web3.contract import Contract + +from src.web3py.contract_tweak import Contract class ContractInterface(Contract): diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py index 072104c6f..464d154a2 100644 --- a/src/providers/execution/contracts/accounting_oracle.py +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -3,7 +3,7 @@ from web3.types import TxParams, BlockIdentifier -from src.modules.accounting.typings import AccountingProcessingState +from src.modules.accounting.types import AccountingProcessingState from src.providers.execution.contracts.base_oracle import BaseOracleContract from src.utils.abi import named_tuple_to_dataclass diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py index 996c272d3..406dbdb23 100644 --- a/src/providers/execution/contracts/base_oracle.py +++ b/src/providers/execution/contracts/base_oracle.py @@ -5,7 +5,8 @@ from web3.types import TxParams, BlockIdentifier from src.providers.execution.base_interface import ContractInterface -from src.typings import SlotNumber +from src.types import SlotNumber + logger = logging.getLogger(__name__) diff --git a/src/providers/execution/contracts/burner.py b/src/providers/execution/contracts/burner.py index 850b057e1..fdb9579fb 100644 --- a/src/providers/execution/contracts/burner.py +++ b/src/providers/execution/contracts/burner.py @@ -3,7 +3,7 @@ from web3.types import BlockIdentifier -from src.modules.accounting.typings import SharesRequestedToBurn +from src.modules.accounting.types import SharesRequestedToBurn from src.providers.execution.base_interface import ContractInterface from src.utils.abi import named_tuple_to_dataclass diff --git a/src/providers/execution/contracts/exit_bus_oracle.py b/src/providers/execution/contracts/exit_bus_oracle.py index 19a64e56d..e876da934 100644 --- a/src/providers/execution/contracts/exit_bus_oracle.py +++ b/src/providers/execution/contracts/exit_bus_oracle.py @@ -4,7 +4,7 @@ from web3.types import BlockIdentifier -from src.modules.ejector.typings import EjectorProcessingState +from src.modules.ejector.types import EjectorProcessingState from src.providers.execution.contracts.base_oracle import BaseOracleContract from src.utils.abi import named_tuple_to_dataclass diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py index 974b0f50e..ef1dc9ef7 100644 --- a/src/providers/execution/contracts/hash_consensus.py +++ b/src/providers/execution/contracts/hash_consensus.py @@ -4,7 +4,7 @@ from eth_typing import ChecksumAddress, Hash32 from web3.types import TxParams, BlockIdentifier -from src.modules.submodules.typings import ChainConfig, CurrentFrame, FrameConfig +from src.modules.submodules.types import ChainConfig, CurrentFrame, FrameConfig from src.providers.execution.base_interface import ContractInterface from src.utils.abi import named_tuple_to_dataclass diff --git a/src/providers/execution/contracts/lido.py b/src/providers/execution/contracts/lido.py index f6fcffd26..5a97cb97b 100644 --- a/src/providers/execution/contracts/lido.py +++ b/src/providers/execution/contracts/lido.py @@ -3,7 +3,7 @@ from eth_typing import ChecksumAddress from web3.types import Gwei, Wei, BlockIdentifier -from src.modules.accounting.typings import LidoReportRebase +from src.modules.accounting.types import LidoReportRebase from src.providers.execution.base_interface import ContractInterface diff --git a/src/providers/execution/contracts/oracle_report_sanity_checker.py b/src/providers/execution/contracts/oracle_report_sanity_checker.py index eae47d849..c2231e937 100644 --- a/src/providers/execution/contracts/oracle_report_sanity_checker.py +++ b/src/providers/execution/contracts/oracle_report_sanity_checker.py @@ -3,7 +3,7 @@ from web3.types import BlockIdentifier -from src.modules.accounting.typings import OracleReportLimits +from src.modules.accounting.types import OracleReportLimits from src.providers.execution.base_interface import ContractInterface from src.utils.abi import named_tuple_to_dataclass diff --git a/src/providers/execution/contracts/staking_router.py b/src/providers/execution/contracts/staking_router.py index ab889864e..ac45815a0 100644 --- a/src/providers/execution/contracts/staking_router.py +++ b/src/providers/execution/contracts/staking_router.py @@ -29,6 +29,7 @@ def get_staking_modules(self, block_identifier: BlockIdentifier = 'latest') -> l return response @lru_cache(maxsize=1) + @list_of_dataclasses(NodeOperator) def get_all_node_operator_digests(self, module_id: int, block_identifier: BlockIdentifier = 'latest') -> list[NodeOperator]: """ Returns node operator digest for each node operator in lido protocol diff --git a/src/providers/execution/contracts/withdrawal_queue_nft.py b/src/providers/execution/contracts/withdrawal_queue_nft.py index 56b118626..3246a0182 100644 --- a/src/providers/execution/contracts/withdrawal_queue_nft.py +++ b/src/providers/execution/contracts/withdrawal_queue_nft.py @@ -3,7 +3,7 @@ from web3.types import Wei, BlockIdentifier -from src.modules.accounting.typings import BatchState, WithdrawalRequestStatus +from src.modules.accounting.types import BatchState, WithdrawalRequestStatus from src.providers.execution.base_interface import ContractInterface from src.utils.abi import named_tuple_to_dataclass @@ -107,7 +107,7 @@ def max_batches_length(self, block_identifier: BlockIdentifier = 'latest') -> in """ maximal length of the batch array provided for prefinalization. """ - response = self.functions.MAX_BATCHES_LENGTH().call() + response = self.functions.MAX_BATCHES_LENGTH().call(block_identifier=block_identifier) logger.info({ 'msg': 'Call `MAX_BATCHES_LENGTH()`.', diff --git a/src/providers/keys/client.py b/src/providers/keys/client.py index 8d3c116d2..ff269f3cc 100644 --- a/src/providers/keys/client.py +++ b/src/providers/keys/client.py @@ -3,8 +3,8 @@ from src.metrics.prometheus.basic import KEYS_API_REQUESTS_DURATION, KEYS_API_LATEST_BLOCKNUMBER from src.providers.http_provider import HTTPProvider -from src.providers.keys.typings import LidoKey, KeysApiStatus -from src.typings import BlockStamp +from src.providers.keys.types import LidoKey, KeysApiStatus +from src.types import BlockStamp from src.utils.dataclass import list_of_dataclasses from src.utils.cache import global_lru_cache as lru_cache diff --git a/src/providers/keys/typings.py b/src/providers/keys/types.py similarity index 100% rename from src/providers/keys/typings.py rename to src/providers/keys/types.py diff --git a/src/services/bunker.py b/src/services/bunker.py index 05aa2c7fb..6060d310a 100644 --- a/src/services/bunker.py +++ b/src/services/bunker.py @@ -11,12 +11,12 @@ from src.services.bunker_cases.abnormal_cl_rebase import AbnormalClRebase from src.services.bunker_cases.midterm_slashing_penalty import MidtermSlashingPenalty -from src.modules.accounting.typings import LidoReportRebase +from src.modules.accounting.types import LidoReportRebase from src.modules.submodules.consensus import FrameConfig, ChainConfig -from src.services.bunker_cases.typings import BunkerConfig +from src.services.bunker_cases.types import BunkerConfig from src.services.safe_border import filter_slashed_validators -from src.typings import BlockStamp, ReferenceBlockStamp, Gwei -from src.web3py.typings import Web3 +from src.types import BlockStamp, ReferenceBlockStamp, Gwei +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/services/bunker_cases/abnormal_cl_rebase.py b/src/services/bunker_cases/abnormal_cl_rebase.py index 24aafc41d..9d91b69b0 100644 --- a/src/services/bunker_cases/abnormal_cl_rebase.py +++ b/src/services/bunker_cases/abnormal_cl_rebase.py @@ -6,15 +6,15 @@ from web3.types import EventData from src.constants import MAX_EFFECTIVE_BALANCE, EFFECTIVE_BALANCE_INCREMENT -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import Validator -from src.providers.keys.typings import LidoKey -from src.services.bunker_cases.typings import BunkerConfig -from src.typings import ReferenceBlockStamp, Gwei, BlockNumber, SlotNumber, BlockStamp, EpochNumber +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import Validator +from src.providers.keys.types import LidoKey +from src.services.bunker_cases.types import BunkerConfig +from src.types import ReferenceBlockStamp, Gwei, BlockNumber, SlotNumber, BlockStamp, EpochNumber from src.utils.slot import get_blockstamp, get_reference_blockstamp from src.utils.validator_state import calculate_active_effective_balance_sum from src.web3py.extensions.lido_validators import LidoValidator, LidoValidatorsProvider -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/services/bunker_cases/midterm_slashing_penalty.py b/src/services/bunker_cases/midterm_slashing_penalty.py index 690031b62..721238378 100644 --- a/src/services/bunker_cases/midterm_slashing_penalty.py +++ b/src/services/bunker_cases/midterm_slashing_penalty.py @@ -7,9 +7,9 @@ PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE ) -from src.modules.submodules.typings import FrameConfig, ChainConfig -from src.providers.consensus.typings import Validator -from src.typings import EpochNumber, Gwei, ReferenceBlockStamp, FrameNumber, SlotNumber +from src.modules.submodules.types import FrameConfig, ChainConfig +from src.providers.consensus.types import Validator +from src.types import EpochNumber, Gwei, ReferenceBlockStamp, FrameNumber, SlotNumber from src.utils.validator_state import calculate_total_active_effective_balance from src.web3py.extensions.lido_validators import LidoValidator diff --git a/src/services/bunker_cases/typings.py b/src/services/bunker_cases/types.py similarity index 100% rename from src/services/bunker_cases/typings.py rename to src/services/bunker_cases/types.py diff --git a/src/services/exit_order_iterator.py b/src/services/exit_order_iterator.py index 3822986ac..f07fdc1bc 100644 --- a/src/services/exit_order_iterator.py +++ b/src/services/exit_order_iterator.py @@ -5,13 +5,13 @@ from eth_typing import ChecksumAddress from src.metrics.prometheus.duration_meter import duration_meter -from src.modules.submodules.typings import ChainConfig +from src.modules.submodules.types import ChainConfig from src.services.exit_order_iterator_state import ExitOrderIteratorStateService, NodeOperatorPredictableState -from src.typings import ReferenceBlockStamp, NodeOperatorGlobalIndex, StakingModuleId, NodeOperatorId +from src.types import ReferenceBlockStamp, NodeOperatorGlobalIndex, StakingModuleId, NodeOperatorId from src.utils.validator_state import get_validator_age from src.web3py.extensions.lido_validators import LidoValidator -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/services/exit_order_iterator_state.py b/src/services/exit_order_iterator_state.py index d23e6b87c..ef874f66f 100644 --- a/src/services/exit_order_iterator_state.py +++ b/src/services/exit_order_iterator_state.py @@ -3,12 +3,12 @@ from more_itertools import ilen from src.constants import TOTAL_BASIS_POINTS -from src.modules.submodules.typings import ChainConfig +from src.modules.submodules.types import ChainConfig from src.services.validator_state import LidoValidatorStateService -from src.typings import ReferenceBlockStamp +from src.types import ReferenceBlockStamp from src.utils.validator_state import is_on_exit, get_validator_age from src.web3py.extensions.lido_validators import NodeOperatorGlobalIndex, LidoValidator -from src.web3py.typings import Web3 +from src.web3py.types import Web3 @dataclass @@ -23,8 +23,8 @@ class NodeOperatorPredictableState: class ExitOrderIteratorStateService(LidoValidatorStateService): """Service prepares lido operator statistic, which used to form validators queue in right order""" - def __init__(self, web3: Web3, blockstamp: ReferenceBlockStamp): - super().__init__(web3) + def __init__(self, w3: Web3, blockstamp: ReferenceBlockStamp): + super().__init__(w3) self._operators = self.w3.lido_validators.get_lido_node_operators(blockstamp) self._operator_validators = self.w3.lido_validators.get_lido_validators_by_node_operators(blockstamp) @@ -108,8 +108,8 @@ def get_total_predictable_validators_count( ) def get_operator_network_penetration_threshold(self, blockstamp: ReferenceBlockStamp) -> float: - exiting_keys_delayed_border_in_slots_bytes = self.w3.lido_contracts.oracle_daemon_config.node_operator_network_penetration_threshold_bp(blockstamp.block_hash) - return exiting_keys_delayed_border_in_slots_bytes / TOTAL_BASIS_POINTS + delay_border = self.w3.lido_contracts.oracle_daemon_config.node_operator_network_penetration_threshold_bp(blockstamp.block_hash) + return delay_border / TOTAL_BASIS_POINTS @staticmethod def count_operator_validators_stats( diff --git a/src/services/prediction.py b/src/services/prediction.py index 91ac3238e..2099f40a2 100644 --- a/src/services/prediction.py +++ b/src/services/prediction.py @@ -2,10 +2,10 @@ from web3.types import Wei, EventData -from src.modules.submodules.typings import ChainConfig -from src.typings import ReferenceBlockStamp +from src.modules.submodules.types import ChainConfig +from src.types import ReferenceBlockStamp from src.utils.events import get_events_in_past -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/services/safe_border.py b/src/services/safe_border.py index f46f9e5b9..06d30c83b 100644 --- a/src/services/safe_border.py +++ b/src/services/safe_border.py @@ -6,12 +6,12 @@ from src.constants import EPOCHS_PER_SLASHINGS_VECTOR, MIN_VALIDATOR_WITHDRAWABILITY_DELAY from src.metrics.prometheus.duration_meter import duration_meter from src.modules.submodules.consensus import ChainConfig, FrameConfig -from src.modules.accounting.typings import OracleReportLimits +from src.modules.accounting.types import OracleReportLimits from src.utils.web3converter import Web3Converter from src.utils.abi import named_tuple_to_dataclass -from src.typings import EpochNumber, FrameNumber, ReferenceBlockStamp, SlotNumber +from src.types import EpochNumber, FrameNumber, ReferenceBlockStamp, SlotNumber from src.web3py.extensions.lido_validators import Validator -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from src.utils.slot import get_blockstamp diff --git a/src/services/validator_state.py b/src/services/validator_state.py index fe790e2e0..f41bb20d4 100644 --- a/src/services/validator_state.py +++ b/src/services/validator_state.py @@ -12,9 +12,9 @@ ACCOUNTING_DELAYED_VALIDATORS, ) from src.modules.accounting.extra_data import ExtraDataService, ExtraData -from src.modules.accounting.typings import OracleReportLimits -from src.modules.submodules.typings import ChainConfig -from src.typings import BlockStamp, ReferenceBlockStamp, EpochNumber +from src.modules.accounting.types import OracleReportLimits +from src.modules.submodules.types import ChainConfig +from src.types import BlockStamp, ReferenceBlockStamp, EpochNumber from src.utils.abi import named_tuple_to_dataclass from src.utils.events import get_events_in_past from src.utils.types import bytes_to_hex_str @@ -25,7 +25,7 @@ LidoValidator, StakingModule, ) -from src.web3py.typings import Web3 +from src.web3py.types import Web3 logger = logging.getLogger(__name__) diff --git a/src/services/withdrawal.py b/src/services/withdrawal.py index 16c6bc012..540492915 100644 --- a/src/services/withdrawal.py +++ b/src/services/withdrawal.py @@ -2,11 +2,11 @@ from src.metrics.prometheus.business import CONTRACT_ON_PAUSE from src.variables import FINALIZATION_BATCH_MAX_REQUEST_COUNT -from src.web3py.typings import Web3 -from src.typings import ReferenceBlockStamp +from src.web3py.types import Web3 +from src.types import ReferenceBlockStamp from src.services.safe_border import SafeBorder from src.modules.submodules.consensus import ChainConfig, FrameConfig -from src.modules.accounting.typings import BatchState +from src.modules.accounting.types import BatchState class Withdrawal: @@ -82,7 +82,7 @@ def _calculate_finalization_batches( state = BatchState( remaining_eth_budget=available_eth, finished=False, - batches=[0] * max_length, + batches=tuple([0] * max_length), batches_length=0 ) @@ -92,7 +92,7 @@ def _calculate_finalization_batches( until_timestamp, FINALIZATION_BATCH_MAX_REQUEST_COUNT, state.as_tuple(), - self.blockstamp.block_hash + self.blockstamp.block_hash, ) return list(filter(lambda value: value > 0, state.batches)) diff --git a/src/typings.py b/src/types.py similarity index 100% rename from src/typings.py rename to src/types.py diff --git a/src/utils/blockstamp.py b/src/utils/blockstamp.py index 909ffefff..5bd7c025a 100644 --- a/src/utils/blockstamp.py +++ b/src/utils/blockstamp.py @@ -1,7 +1,7 @@ from web3.types import Timestamp -from src.providers.consensus.typings import BlockDetailsResponse -from src.typings import SlotNumber, EpochNumber, ReferenceBlockStamp, BlockStamp, BlockNumber +from src.providers.consensus.types import BlockDetailsResponse +from src.types import SlotNumber, EpochNumber, ReferenceBlockStamp, BlockStamp, BlockNumber def build_reference_blockstamp( diff --git a/src/utils/events.py b/src/utils/events.py index a881a39b4..f868dcb18 100644 --- a/src/utils/events.py +++ b/src/utils/events.py @@ -1,6 +1,6 @@ from web3.contract.contract import ContractEvent -from src.typings import ReferenceBlockStamp +from src.types import ReferenceBlockStamp def get_events_in_past( diff --git a/src/utils/slot.py b/src/utils/slot.py index 620e21a9b..ced52d0e9 100644 --- a/src/utils/slot.py +++ b/src/utils/slot.py @@ -2,9 +2,9 @@ from http import HTTPStatus from src.providers.consensus.client import ConsensusClient -from src.providers.consensus.typings import BlockHeaderFullResponse, BlockDetailsResponse +from src.providers.consensus.types import BlockHeaderFullResponse, BlockDetailsResponse from src.providers.http_provider import NotOkResponse -from src.typings import SlotNumber, EpochNumber, ReferenceBlockStamp +from src.types import SlotNumber, EpochNumber, ReferenceBlockStamp from src.utils.blockstamp import build_reference_blockstamp, build_blockstamp logger = logging.getLogger(__name__) diff --git a/src/utils/validator_state.py b/src/utils/validator_state.py index c34bb0c9b..d5981612f 100644 --- a/src/utils/validator_state.py +++ b/src/utils/validator_state.py @@ -7,8 +7,8 @@ FAR_FUTURE_EPOCH, EFFECTIVE_BALANCE_INCREMENT, ) -from src.providers.consensus.typings import Validator -from src.typings import EpochNumber, Gwei +from src.providers.consensus.types import Validator +from src.types import EpochNumber, Gwei def is_active_validator(validator: Validator, epoch: EpochNumber) -> bool: diff --git a/src/utils/web3converter.py b/src/utils/web3converter.py index 176e5fa4e..ec70b97da 100644 --- a/src/utils/web3converter.py +++ b/src/utils/web3converter.py @@ -1,5 +1,5 @@ -from src.typings import SlotNumber, EpochNumber, FrameNumber -from src.modules.submodules.typings import ChainConfig, FrameConfig +from src.types import SlotNumber, EpochNumber, FrameNumber +from src.modules.submodules.types import ChainConfig, FrameConfig class Web3Converter: diff --git a/src/web3py/extensions/contracts.py b/src/web3py/extensions/contracts.py index ac48599eb..d207a8df2 100644 --- a/src/web3py/extensions/contracts.py +++ b/src/web3py/extensions/contracts.py @@ -19,7 +19,7 @@ from src.providers.execution.contracts.oracle_report_sanity_checker import OracleReportSanityCheckerContract from src.providers.execution.contracts.staking_router import StakingRouterContract from src.providers.execution.contracts.withdrawal_queue_nft import WithdrawalQueueNftContract -from src.typings import BlockStamp, SlotNumber +from src.types import BlockStamp, SlotNumber from src.utils.cache import global_lru_cache as lru_cache diff --git a/src/web3py/extensions/lido_validators.py b/src/web3py/extensions/lido_validators.py index 0e031106c..2aaf13dcb 100644 --- a/src/web3py/extensions/lido_validators.py +++ b/src/web3py/extensions/lido_validators.py @@ -5,9 +5,9 @@ from eth_typing import ChecksumAddress from web3.module import Module -from src.providers.consensus.typings import Validator -from src.providers.keys.typings import LidoKey -from src.typings import BlockStamp, StakingModuleId, NodeOperatorId, NodeOperatorGlobalIndex +from src.providers.consensus.types import Validator +from src.providers.keys.types import LidoKey +from src.types import BlockStamp, StakingModuleId, NodeOperatorId, NodeOperatorGlobalIndex from src.utils.dataclass import Nested from src.utils.cache import global_lru_cache as lru_cache @@ -16,7 +16,7 @@ if TYPE_CHECKING: - from src.web3py.typings import Web3 # pragma: no cover + from src.web3py.types import Web3 # pragma: no cover @dataclass @@ -172,8 +172,6 @@ def get_lido_node_operators(self, blockstamp: BlockStamp) -> list[NodeOperator]: for module in self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash): operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module.id, blockstamp.block_hash) - - for operator in operators: - result.append(NodeOperator.from_response(operator, module)) + result.extend(operators) return result diff --git a/src/web3py/typings.py b/src/web3py/types.py similarity index 100% rename from src/web3py/typings.py rename to src/web3py/types.py diff --git a/tests/conftest.py b/tests/conftest.py index a31f704c2..78fd3421c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,9 +11,9 @@ import src.variables from src.variables import CONSENSUS_CLIENT_URI, EXECUTION_CLIENT_URI, KEYS_API_URI -from src.typings import BlockStamp, SlotNumber, BlockNumber, EpochNumber, ReferenceBlockStamp +from src.types import BlockStamp, SlotNumber, BlockNumber, EpochNumber, ReferenceBlockStamp from src.web3py.extensions import LidoContracts, TransactionUtils, LidoValidatorsProvider -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from src.web3py.contract_tweak import tweak_w3_contracts from tests.providers import ( @@ -153,7 +153,7 @@ def get_blockstamp_by_state(w3, state_id) -> BlockStamp: state_root=slot_details.message.state_root, block_number=BlockNumber(int(slot_details.message.body['execution_payload']['block_number'])), block_hash=slot_details.message.body['execution_payload']['block_hash'], - block_timestamp=Timestamp(slot_details.message.body['execution_payload']['timestamp']), + block_timestamp=Timestamp(int(slot_details.message.body['execution_payload']['timestamp'])), ref_slot=SlotNumber(int(slot_details.message.slot)), ref_epoch=EpochNumber(int(int(slot_details.message.slot) / 12)), ) diff --git a/tests/factory/blockstamp.py b/tests/factory/blockstamp.py index 12501be09..c37d6b66b 100644 --- a/tests/factory/blockstamp.py +++ b/tests/factory/blockstamp.py @@ -2,7 +2,7 @@ from web3.types import Timestamp from tests.factory.web3_factory import Web3Factory -from src.typings import BlockStamp, StateRoot, SlotNumber, BlockHash, ReferenceBlockStamp, EpochNumber +from src.types import BlockStamp, StateRoot, SlotNumber, BlockHash, ReferenceBlockStamp, EpochNumber class BlockStampFactory(Web3Factory): diff --git a/tests/factory/configs.py b/tests/factory/configs.py index 3b120a387..e33c1c809 100644 --- a/tests/factory/configs.py +++ b/tests/factory/configs.py @@ -1,8 +1,8 @@ from typing import overload -from src.modules.accounting.typings import OracleReportLimits -from src.modules.submodules.typings import ChainConfig, FrameConfig -from src.providers.consensus.typings import BeaconSpecResponse -from src.services.bunker_cases.typings import BunkerConfig +from src.modules.accounting.types import OracleReportLimits +from src.modules.submodules.types import ChainConfig, FrameConfig +from src.providers.consensus.types import BeaconSpecResponse +from src.services.bunker_cases.types import BunkerConfig from tests.factory.web3_factory import Web3Factory diff --git a/tests/factory/contract_responses.py b/tests/factory/contract_responses.py index 1fa93349e..250be7a2d 100644 --- a/tests/factory/contract_responses.py +++ b/tests/factory/contract_responses.py @@ -1,4 +1,4 @@ -from src.modules.accounting.typings import LidoReportRebase +from src.modules.accounting.types import LidoReportRebase from tests.factory.web3_factory import Web3Factory diff --git a/tests/factory/member_info.py b/tests/factory/member_info.py index be5a67c45..dcdc2a283 100644 --- a/tests/factory/member_info.py +++ b/tests/factory/member_info.py @@ -1,4 +1,4 @@ -from src.modules.submodules.typings import MemberInfo +from src.modules.submodules.types import MemberInfo from tests.factory.web3_factory import Web3Factory diff --git a/tests/factory/no_registry.py b/tests/factory/no_registry.py index 5450539f9..c02a8f24a 100644 --- a/tests/factory/no_registry.py +++ b/tests/factory/no_registry.py @@ -4,8 +4,8 @@ from faker import Faker from pydantic_factories import Use -from src.providers.consensus.typings import Validator, ValidatorState -from src.providers.keys.typings import LidoKey +from src.providers.consensus.types import Validator, ValidatorState +from src.providers.keys.types import LidoKey from tests.factory.web3_factory import Web3Factory from src.web3py.extensions.lido_validators import StakingModule, LidoValidator, NodeOperator diff --git a/tests/modules/accounting/bunker/conftest.py b/tests/modules/accounting/bunker/conftest.py index 2d7a3680c..68b01e80e 100644 --- a/tests/modules/accounting/bunker/conftest.py +++ b/tests/modules/accounting/bunker/conftest.py @@ -2,13 +2,13 @@ import pytest -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import Validator, ValidatorStatus, ValidatorState +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import Validator, ValidatorStatus, ValidatorState from src.services.bunker import BunkerService -from src.providers.keys.typings import LidoKey +from src.providers.keys.types import LidoKey from src.services.bunker_cases.abnormal_cl_rebase import AbnormalClRebase -from src.services.bunker_cases.typings import BunkerConfig -from src.typings import BlockNumber, BlockStamp, ReferenceBlockStamp +from src.services.bunker_cases.types import BunkerConfig +from src.types import BlockNumber, BlockStamp, ReferenceBlockStamp def simple_ref_blockstamp(block_number: int) -> ReferenceBlockStamp: @@ -123,18 +123,6 @@ def _get_eth_distributed_events(from_block: BlockNumber, to_block: BlockNumber): abnormal_case._get_eth_distributed_events = Mock(side_effect=_get_eth_distributed_events) -@pytest.fixture -def mock_get_total_supply(bunker): - def _get_total_supply(block: BlockStamp): - supplies = { - 0: 15 * 10**18, - } - - return supplies[block.block_number] - - bunker._get_total_supply = Mock(side_effect=_get_total_supply) - - @pytest.fixture def mock_get_withdrawal_vault_balance(abnormal_case, contracts): def _get_withdrawal_vault_balance(blockstamp: BlockStamp): diff --git a/tests/modules/accounting/bunker/test_bunker.py b/tests/modules/accounting/bunker/test_bunker.py index 4a71538be..d444d7732 100644 --- a/tests/modules/accounting/bunker/test_bunker.py +++ b/tests/modules/accounting/bunker/test_bunker.py @@ -3,9 +3,9 @@ import pytest -from src.modules.accounting.typings import LidoReportRebase +from src.modules.accounting.types import LidoReportRebase from src.services.bunker import BunkerService -from src.typings import ReferenceBlockStamp +from src.types import ReferenceBlockStamp from src.web3py.extensions.lido_validators import LidoValidator from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.configs import BunkerConfigFactory, ChainConfigFactory, FrameConfigFactory @@ -199,10 +199,12 @@ def is_abnormal_cl_rebase(self, monkeypatch: pytest.MonkeyPatch) -> Iterable[Moc ) def test_get_cl_rebase_for_frame( bunker, - mock_get_total_supply, + contracts, simulated_post_total_pooled_ether, expected_rebase, ): + bunker.w3.lido_contracts.lido.total_supply = Mock(return_value=15 * 10 ** 18) + blockstamp = simple_ref_blockstamp(0) simulated_cl_rebase = LidoReportRebase( post_total_pooled_ether=simulated_post_total_pooled_ether, diff --git a/tests/modules/accounting/bunker/test_bunker_abnormal_cl_rebase.py b/tests/modules/accounting/bunker/test_bunker_abnormal_cl_rebase.py index 01d65c02c..a85c46288 100644 --- a/tests/modules/accounting/bunker/test_bunker_abnormal_cl_rebase.py +++ b/tests/modules/accounting/bunker/test_bunker_abnormal_cl_rebase.py @@ -1,9 +1,9 @@ import pytest from src.constants import FAR_FUTURE_EPOCH -from src.providers.consensus.typings import Validator, ValidatorStatus, ValidatorState +from src.providers.consensus.types import Validator, ValidatorStatus, ValidatorState from src.services.bunker_cases.abnormal_cl_rebase import AbnormalClRebase -from src.services.bunker_cases.typings import BunkerConfig +from src.services.bunker_cases.types import BunkerConfig from tests.modules.accounting.bunker.conftest import simple_ref_blockstamp, simple_key, simple_blockstamp diff --git a/tests/modules/accounting/bunker/test_bunker_medterm_penalty.py b/tests/modules/accounting/bunker/test_bunker_medterm_penalty.py index 88a498ff7..a7fc0a4c4 100644 --- a/tests/modules/accounting/bunker/test_bunker_medterm_penalty.py +++ b/tests/modules/accounting/bunker/test_bunker_medterm_penalty.py @@ -1,10 +1,10 @@ import pytest from src.modules.submodules.consensus import FrameConfig -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import Validator, ValidatorStatus, ValidatorState +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import Validator, ValidatorStatus, ValidatorState from src.services.bunker_cases.midterm_slashing_penalty import MidtermSlashingPenalty -from src.typings import EpochNumber, ReferenceBlockStamp +from src.types import EpochNumber, ReferenceBlockStamp def simple_blockstamp( diff --git a/tests/modules/accounting/test_accounting_module.py b/tests/modules/accounting/test_accounting_module.py index c4201c64a..2b25bff92 100644 --- a/tests/modules/accounting/test_accounting_module.py +++ b/tests/modules/accounting/test_accounting_module.py @@ -9,11 +9,11 @@ from src.modules.accounting import accounting as accounting_module from src.modules.accounting.accounting import Accounting from src.modules.accounting.accounting import logger as accounting_logger -from src.modules.accounting.typings import LidoReportRebase +from src.modules.accounting.types import LidoReportRebase from src.modules.submodules.oracle_module import ModuleExecuteDelay -from src.modules.submodules.typings import ChainConfig, FrameConfig +from src.modules.submodules.types import ChainConfig, FrameConfig from src.services.withdrawal import Withdrawal -from src.typings import BlockStamp, ReferenceBlockStamp +from src.types import BlockStamp, ReferenceBlockStamp from src.web3py.extensions.lido_validators import NodeOperatorId, StakingModule from tests.factory.blockstamp import BlockStampFactory, ReferenceBlockStampFactory from tests.factory.configs import ChainConfigFactory, FrameConfigFactory diff --git a/tests/modules/accounting/test_safe_border_integration.py b/tests/modules/accounting/test_safe_border_integration.py index 437d1fc65..cabdc2f37 100644 --- a/tests/modules/accounting/test_safe_border_integration.py +++ b/tests/modules/accounting/test_safe_border_integration.py @@ -2,7 +2,7 @@ import pytest -from src.typings import ReferenceBlockStamp +from src.types import ReferenceBlockStamp from src.services.safe_border import SafeBorder from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.configs import ChainConfigFactory, FrameConfigFactory, OracleReportLimitsFactory diff --git a/tests/modules/accounting/test_safe_border_unit.py b/tests/modules/accounting/test_safe_border_unit.py index 8988de279..433fcc7fe 100644 --- a/tests/modules/accounting/test_safe_border_unit.py +++ b/tests/modules/accounting/test_safe_border_unit.py @@ -4,7 +4,7 @@ from unittest.mock import Mock from src.services.safe_border import SafeBorder from src.web3py.extensions.lido_validators import Validator -from src.providers.consensus.typings import ValidatorState +from src.providers.consensus.types import ValidatorState from src.modules.submodules.consensus import ChainConfig, FrameConfig from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.configs import OracleReportLimitsFactory diff --git a/tests/modules/accounting/test_validator_state.py b/tests/modules/accounting/test_validator_state.py index 09725e88a..43e3de034 100644 --- a/tests/modules/accounting/test_validator_state.py +++ b/tests/modules/accounting/test_validator_state.py @@ -6,10 +6,10 @@ from src.constants import FAR_FUTURE_EPOCH from src.services.validator_state import LidoValidatorStateService -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import Validator, ValidatorState -from src.providers.keys.typings import LidoKey -from src.typings import StakingModuleId, NodeOperatorId +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import Validator, ValidatorState +from src.providers.keys.types import LidoKey +from src.types import StakingModuleId, NodeOperatorId from src.web3py.extensions.lido_validators import ( NodeOperator, StakingModule, diff --git a/tests/modules/accounting/test_withdrawal_integration.py b/tests/modules/accounting/test_withdrawal_integration.py index 3f1fb69e9..a5c9b422a 100644 --- a/tests/modules/accounting/test_withdrawal_integration.py +++ b/tests/modules/accounting/test_withdrawal_integration.py @@ -1,6 +1,6 @@ import pytest -from src.modules.submodules.typings import FrameConfig, ChainConfig +from src.modules.submodules.types import FrameConfig, ChainConfig from src.services.withdrawal import Withdrawal from src.constants import SHARE_RATE_PRECISION_E27 from tests.conftest import get_blockstamp_by_state diff --git a/tests/modules/accounting/test_withdrawal_unit.py b/tests/modules/accounting/test_withdrawal_unit.py index 3caed23dd..b24da0b0b 100644 --- a/tests/modules/accounting/test_withdrawal_unit.py +++ b/tests/modules/accounting/test_withdrawal_unit.py @@ -5,7 +5,7 @@ from src.modules.submodules.consensus import ChainConfig, FrameConfig from tests.conftest import get_blockstamp_by_state from src.constants import SHARE_RATE_PRECISION_E27 -from src.modules.accounting.typings import BatchState +from src.modules.accounting.types import BatchState from tests.factory.configs import OracleReportLimitsFactory @@ -61,7 +61,7 @@ def test_returns_batch_if_there_are_finalizable_requests(subject: Withdrawal): def test_calculate_finalization_batches(subject: Withdrawal, past_blockstamp): state_initial = BatchState(remaining_eth_budget=100, finished=False, batches=[1] + [0] * 35, batches_length=1) state_final = BatchState(remaining_eth_budget=100, finished=True, batches=[2] + [0] * 35, batches_length=2) - subject._fetch_finalization_batches = Mock(side_effect=[state_initial, state_final]) + subject.w3.lido_contracts.withdrawal_queue_nft.calculate_finalization_batches = Mock(side_effect=[state_initial, state_final]) subject.w3.lido_contracts.withdrawal_queue_nft.max_batches_length = Mock(return_value=36) result = subject._calculate_finalization_batches(1, SHARE_RATE_PRECISION_E27, past_blockstamp.block_timestamp) diff --git a/tests/modules/ejector/test_data_encode.py b/tests/modules/ejector/test_data_encode.py index 2645f05d1..4d03a64a8 100644 --- a/tests/modules/ejector/test_data_encode.py +++ b/tests/modules/ejector/test_data_encode.py @@ -12,7 +12,7 @@ encode_data, sort_validators_to_eject, ) -from src.typings import StakingModuleId, NodeOperatorId +from src.types import StakingModuleId, NodeOperatorId from src.web3py.extensions.lido_validators import LidoValidator from tests.factory.no_registry import LidoValidatorFactory diff --git a/tests/modules/ejector/test_ejector.py b/tests/modules/ejector/test_ejector.py index 81612c9c5..2613b1c10 100644 --- a/tests/modules/ejector/test_ejector.py +++ b/tests/modules/ejector/test_ejector.py @@ -8,11 +8,11 @@ from src.modules.ejector.ejector import Ejector, EjectorProcessingState from src.modules.ejector.ejector import logger as ejector_logger from src.modules.submodules.oracle_module import ModuleExecuteDelay -from src.modules.submodules.typings import ChainConfig -from src.typings import BlockStamp, ReferenceBlockStamp +from src.modules.submodules.types import ChainConfig +from src.types import BlockStamp, ReferenceBlockStamp from src.web3py.extensions.contracts import LidoContracts from src.web3py.extensions.lido_validators import NodeOperatorId, StakingModuleId -from src.web3py.typings import Web3 +from src.web3py.types import Web3 from tests.factory.blockstamp import BlockStampFactory, ReferenceBlockStampFactory from tests.factory.configs import ChainConfigFactory from tests.factory.no_registry import LidoValidatorFactory @@ -65,7 +65,7 @@ def test_ejector_execute_module(ejector: Ejector, blockstamp: BlockStamp) -> Non def test_ejector_execute_module_on_pause(ejector: Ejector, blockstamp: BlockStamp) -> None: ejector.get_blockstamp_for_report = Mock(return_value=blockstamp) ejector.build_report = Mock(return_value=(1, 294271, 0, 1, b'')) - ejector._is_paused = Mock(return_value=True) + ejector.w3.lido_contracts.validators_exit_bus_oracle.is_paused = Mock(return_value=True) assert ( ejector.execute_module(last_finalized_blockstamp=blockstamp) is ModuleExecuteDelay.NEXT_SLOT ), "execute_module should wait for the next slot" @@ -159,7 +159,7 @@ def test_simple( @pytest.mark.unit @pytest.mark.usefixtures("contracts") def test_get_unfinalized_steth(ejector: Ejector, blockstamp: BlockStamp) -> None: - result = ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth(blockstamp) + result = ejector.w3.lido_contracts.withdrawal_queue_nft.unfinalized_steth(blockstamp.block_hash) assert result == 8362187000000000000, "Unexpected unfinalized stETH" @@ -177,9 +177,9 @@ def test_compute_activation_exit_epoch( @pytest.mark.unit def test_is_main_data_submitted(ejector: Ejector, blockstamp: BlockStamp) -> None: - ejector._get_processing_state = Mock(return_value=Mock(data_submitted=True)) + ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state = Mock(return_value=Mock(data_submitted=True)) assert ejector.is_main_data_submitted(blockstamp) is True, "Unexpected is_main_data_submitted result" - ejector._get_processing_state.assert_called_once_with(blockstamp) + ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state.assert_called_once_with(blockstamp.block_hash) @pytest.mark.unit @@ -290,25 +290,18 @@ def test_get_sweep_delay_in_epochs( assert result == 1, "Unexpected sweep delay in epochs" -@pytest.mark.unit -@pytest.mark.usefixtures("contracts") -def test_get_reserved_buffer(ejector: Ejector, blockstamp: BlockStamp) -> None: - result = ejector._get_buffer_ether(blockstamp) - assert result == 2991010000000000000010, "Unexpected reserved buffer" - - @pytest.mark.usefixtures("contracts") def test_get_total_balance(ejector: Ejector, blockstamp: BlockStamp) -> None: ejector.w3.lido_contracts.get_withdrawal_balance = Mock(return_value=3) ejector.w3.lido_contracts.get_el_vault_balance = Mock(return_value=17) - ejector._get_buffer_ether = Mock(return_value=1) + ejector.w3.lido_contracts.lido.get_buffered_ether = Mock(return_value=1) result = ejector._get_total_el_balance(blockstamp) assert result == 21, "Unexpected total balance" ejector.w3.lido_contracts.get_withdrawal_balance.assert_called_once_with(blockstamp) ejector.w3.lido_contracts.get_el_vault_balance.assert_called_once_with(blockstamp) - ejector._get_buffer_ether.assert_called_once_with(blockstamp) + ejector.w3.lido_contracts.lido.get_buffered_ether.assert_called_once_with(blockstamp.block_hash) class TestChurnLimit: @@ -368,7 +361,7 @@ def test_get_churn_limit_basic( @pytest.mark.unit def test_get_processing_state(ejector: Ejector, blockstamp: BlockStamp) -> None: - result = ejector._get_processing_state(blockstamp) + result = ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state(blockstamp.block_hash) assert isinstance(result, EjectorProcessingState), "Unexpected processing state response" diff --git a/tests/modules/ejector/test_exit_order_iterator.py b/tests/modules/ejector/test_exit_order_iterator.py index 69cf8e8b9..4c1a31f3d 100644 --- a/tests/modules/ejector/test_exit_order_iterator.py +++ b/tests/modules/ejector/test_exit_order_iterator.py @@ -2,8 +2,8 @@ import pytest -from src.providers.consensus.typings import ValidatorState -from src.providers.keys.typings import LidoKey +from src.providers.consensus.types import ValidatorState +from src.providers.keys.types import LidoKey from src.services.exit_order_iterator import ExitOrderIterator from src.services.exit_order_iterator_state import NodeOperatorPredictableState, ExitOrderIteratorStateService from src.web3py.extensions.lido_validators import LidoValidator, StakingModuleId, NodeOperatorId @@ -139,9 +139,6 @@ def mock_exit_order_iterator_state_service(monkeypatch): class MockedExitOrderIteratorStateService(ExitOrderIteratorStateService): pass - MockedExitOrderIteratorStateService.w3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( - Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) - ) MockedExitOrderIteratorStateService.get_operator_network_penetration_threshold = lambda *_: 0.05 MockedExitOrderIteratorStateService.get_operators_with_last_exited_validator_indexes = lambda *_: {} MockedExitOrderIteratorStateService.get_exitable_lido_validators = lambda *_: [] @@ -155,6 +152,10 @@ class MockedExitOrderIteratorStateService(ExitOrderIteratorStateService): @pytest.mark.unit def test_exit_order_iterator_iter(web3, lido_validators, contracts, mock_exit_order_iterator_state_service): + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( + Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) + ) + iterator = ExitOrderIterator(web3, ReferenceBlockStampFactory.build(), ChainConfigFactory.build()) web3.lido_validators.get_lido_node_operators = lambda _: [] web3.lido_validators.get_lido_validators_by_node_operators = lambda _: [] @@ -172,6 +173,10 @@ def test_exit_order_iterator_iter(web3, lido_validators, contracts, mock_exit_or @pytest.mark.unit def test_exit_order_iterator_next(web3, lido_validators, contracts, mock_exit_order_iterator_state_service): + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( + Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) + ) + iterator = ExitOrderIterator(web3, ReferenceBlockStampFactory.build(), ChainConfigFactory.build()) web3.lido_validators.get_lido_node_operators = lambda _: [] web3.lido_validators.get_lido_validators_by_node_operators = lambda _: [] diff --git a/tests/modules/ejector/test_exit_order_state_service.py b/tests/modules/ejector/test_exit_order_state_service.py index ba16b9150..0cdb24fc0 100644 --- a/tests/modules/ejector/test_exit_order_state_service.py +++ b/tests/modules/ejector/test_exit_order_state_service.py @@ -2,8 +2,8 @@ import pytest -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import ValidatorState, Validator, ValidatorStatus +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import ValidatorState, Validator, ValidatorStatus from src.services.exit_order_iterator import NodeOperatorPredictableState from src.services.exit_order_iterator_state import ExitOrderIteratorStateService from src.web3py.extensions.lido_validators import ( diff --git a/tests/modules/ejector/test_prediction.py b/tests/modules/ejector/test_prediction.py index b30dec55a..9c96b99ce 100644 --- a/tests/modules/ejector/test_prediction.py +++ b/tests/modules/ejector/test_prediction.py @@ -1,13 +1,13 @@ -from unittest.mock import MagicMock +from unittest.mock import MagicMock, Mock import pytest from hexbytes import HexBytes from web3.types import Wei import src.services.prediction as prediction_module -from src.modules.submodules.typings import ChainConfig +from src.modules.submodules.types import ChainConfig from src.services.prediction import RewardsPredictionService -from src.typings import BlockNumber, SlotNumber +from src.types import BlockNumber, SlotNumber from tests.factory.blockstamp import ReferenceBlockStampFactory @@ -280,13 +280,11 @@ def test_get_rewards_prediction(web3, contracts, monkeypatch: pytest.MonkeyPatch genesis_time=0, ) + web3.lido_contracts.oracle_daemon_config.prediction_duration_in_slots = Mock(return_value=12) + SOME_EVENTS = object() + with monkeypatch.context() as m: - m.setattr( - RewardsPredictionService, - "_get_prediction_duration_in_slots", - MagicMock(return_value=12), - ) m.setattr( prediction_module, "get_events_in_past", diff --git a/tests/modules/submodules/consensus/conftest.py b/tests/modules/submodules/consensus/conftest.py index efc263350..37c275fa8 100644 --- a/tests/modules/submodules/consensus/conftest.py +++ b/tests/modules/submodules/consensus/conftest.py @@ -1,7 +1,7 @@ import pytest from src.modules.submodules.consensus import ConsensusModule -from src.typings import BlockStamp, ReferenceBlockStamp +from src.types import BlockStamp, ReferenceBlockStamp class SimpleConsensusModule(ConsensusModule): diff --git a/tests/modules/submodules/consensus/test_consensus.py b/tests/modules/submodules/consensus/test_consensus.py index df0ec608b..353e6879b 100644 --- a/tests/modules/submodules/consensus/test_consensus.py +++ b/tests/modules/submodules/consensus/test_consensus.py @@ -6,9 +6,9 @@ from src import variables from src.modules.submodules import consensus as consensus_module from src.modules.submodules.consensus import ZERO_HASH, ConsensusModule, IsNotMemberException, MemberInfo -from src.modules.submodules.typings import ChainConfig -from src.providers.consensus.typings import BeaconSpecResponse -from src.typings import BlockStamp, ReferenceBlockStamp +from src.modules.submodules.types import ChainConfig +from src.providers.consensus.types import BeaconSpecResponse +from src.types import BlockStamp, ReferenceBlockStamp from tests.conftest import get_blockstamp_by_state, Account from tests.factory.blockstamp import ReferenceBlockStampFactory from tests.factory.configs import BeaconSpecResponseFactory, ChainConfigFactory diff --git a/tests/modules/submodules/consensus/test_reports.py b/tests/modules/submodules/consensus/test_reports.py index cc67f2e37..51dddef30 100644 --- a/tests/modules/submodules/consensus/test_reports.py +++ b/tests/modules/submodules/consensus/test_reports.py @@ -3,8 +3,8 @@ import pytest from hexbytes import HexBytes from src import variables -from src.modules.accounting.typings import ReportData -from src.modules.submodules.typings import ChainConfig, FrameConfig, ZERO_HASH +from src.modules.accounting.types import ReportData +from src.modules.submodules.types import ChainConfig, FrameConfig, ZERO_HASH from tests.conftest import Account from tests.factory.blockstamp import ReferenceBlockStampFactory diff --git a/tests/modules/submodules/test_oracle_module.py b/tests/modules/submodules/test_oracle_module.py index e266ccc8a..f125f1272 100644 --- a/tests/modules/submodules/test_oracle_module.py +++ b/tests/modules/submodules/test_oracle_module.py @@ -11,7 +11,7 @@ from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay from src.providers.http_provider import NotOkResponse from src.providers.keys.client import KeysOutdatedException -from src.typings import BlockStamp +from src.types import BlockStamp from src.utils.slot import InconsistentData, NoSlotsAvailable, SlotNotFinalized from src import variables from tests.factory.blockstamp import ReferenceBlockStampFactory diff --git a/tests/providers_clients/test_consensus_client.py b/tests/providers_clients/test_consensus_client.py index 7423246e7..0bed74745 100644 --- a/tests/providers_clients/test_consensus_client.py +++ b/tests/providers_clients/test_consensus_client.py @@ -4,8 +4,8 @@ import pytest from src.providers.consensus.client import ConsensusClient -from src.providers.consensus.typings import Validator -from src.typings import SlotNumber +from src.providers.consensus.types import Validator +from src.types import SlotNumber from src.utils.blockstamp import build_blockstamp from src.variables import CONSENSUS_CLIENT_URI from tests.factory.blockstamp import BlockStampFactory diff --git a/tests/utils/test_events.py b/tests/utils/test_events.py index 3ab056a47..c6c5e8169 100644 --- a/tests/utils/test_events.py +++ b/tests/utils/test_events.py @@ -1,6 +1,6 @@ import pytest -from src.typings import ReferenceBlockStamp +from src.types import ReferenceBlockStamp from src.utils.events import get_events_in_past from tests.factory.blockstamp import ReferenceBlockStampFactory diff --git a/tests/utils/test_validator_state_utils.py b/tests/utils/test_validator_state_utils.py index 9fdbe8e5a..79e628d0b 100644 --- a/tests/utils/test_validator_state_utils.py +++ b/tests/utils/test_validator_state_utils.py @@ -2,8 +2,8 @@ import pytest from src.constants import FAR_FUTURE_EPOCH, EFFECTIVE_BALANCE_INCREMENT -from src.providers.consensus.typings import Validator, ValidatorStatus, ValidatorState -from src.typings import EpochNumber, Gwei +from src.providers.consensus.types import Validator, ValidatorStatus, ValidatorState +from src.types import EpochNumber, Gwei from src.utils.validator_state import ( calculate_total_active_effective_balance, is_on_exit, diff --git a/tests/utils/test_web3_converter.py b/tests/utils/test_web3_converter.py index 026f86329..b091dfae3 100644 --- a/tests/utils/test_web3_converter.py +++ b/tests/utils/test_web3_converter.py @@ -1,7 +1,7 @@ import pytest -from src.modules.submodules.typings import ChainConfig, FrameConfig -from src.typings import EpochNumber, FrameNumber, SlotNumber +from src.modules.submodules.types import ChainConfig, FrameConfig +from src.types import EpochNumber, FrameNumber, SlotNumber from src.utils.web3converter import Web3Converter from tests.factory.configs import ChainConfigFactory, FrameConfigFactory From 1a50d6ffd6561faa8787e00959d68ca6986712c0 Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 01:02:14 +0200 Subject: [PATCH 04/21] fix get_latest_blockstamp --- tests/modules/submodules/consensus/test_consensus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modules/submodules/consensus/test_consensus.py b/tests/modules/submodules/consensus/test_consensus.py index 353e6879b..561ff01da 100644 --- a/tests/modules/submodules/consensus/test_consensus.py +++ b/tests/modules/submodules/consensus/test_consensus.py @@ -64,7 +64,7 @@ def set_report_account(monkeypatch): @pytest.mark.unit -def test_get_latest_blockstamp(consensus): +def test_get_latest_blockstamp(consensus, set_no_account): bs = consensus._get_latest_blockstamp() assert isinstance(bs, BlockStamp) From 736faf93325e6fb070dae1020bde345b9f3eadfa Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 01:08:39 +0200 Subject: [PATCH 05/21] fix test consensus --- tests/modules/submodules/consensus/test_consensus.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/modules/submodules/consensus/test_consensus.py b/tests/modules/submodules/consensus/test_consensus.py index 561ff01da..9e7af4eef 100644 --- a/tests/modules/submodules/consensus/test_consensus.py +++ b/tests/modules/submodules/consensus/test_consensus.py @@ -119,7 +119,7 @@ def test_get_member_info_submit_only_account(consensus, set_submit_account): # ------ Get block for report tests ---------- @pytest.mark.unit @pytest.mark.possible_integration -def test_get_blockstamp_for_report_slot_not_finalized(web3, consensus, caplog): +def test_get_blockstamp_for_report_slot_not_finalized(web3, consensus, caplog, set_no_account): bs = ReferenceBlockStampFactory.build() current_frame = consensus.get_current_frame(bs) previous_blockstamp = get_blockstamp_by_state(web3, current_frame.ref_slot - 1) @@ -131,7 +131,7 @@ def test_get_blockstamp_for_report_slot_not_finalized(web3, consensus, caplog): @pytest.mark.unit @pytest.mark.possible_integration -def test_get_blockstamp_for_report_slot_deadline_missed(web3, consensus, caplog): +def test_get_blockstamp_for_report_slot_deadline_missed(web3, consensus, caplog, set_no_account): bs = ReferenceBlockStampFactory.build() member_info = consensus.get_member_info(bs) member_info.deadline_slot = bs.slot_number - 1 @@ -164,7 +164,7 @@ def test_get_blockstamp_for_report_contract_is_not_reportable(consensus: Consens @pytest.mark.unit @pytest.mark.possible_integration -def test_get_blockstamp_for_report_slot_member_is_not_in_fast_line_ready(web3, consensus, caplog): +def test_get_blockstamp_for_report_slot_member_is_not_in_fast_line_ready(web3, consensus, caplog, set_no_account): latest_blockstamp = get_blockstamp_by_state(web3, 'head') member_info = consensus.get_member_info(latest_blockstamp) member_info.is_fast_lane = False @@ -177,7 +177,7 @@ def test_get_blockstamp_for_report_slot_member_is_not_in_fast_line_ready(web3, c @pytest.mark.unit @pytest.mark.possible_integration -def test_get_blockstamp_for_report_slot_member_ready_to_report(web3, consensus, caplog): +def test_get_blockstamp_for_report_slot_member_ready_to_report(web3, consensus, caplog, set_no_account): latest_blockstamp = get_blockstamp_by_state(web3, 'head') blockstamp = consensus.get_blockstamp_for_report(latest_blockstamp) assert isinstance(blockstamp, BlockStamp) From ec1edf67532a499237cfde57539c14cdf032ecbd Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 04:10:17 +0200 Subject: [PATCH 06/21] tests fixes --- src/providers/execution/contracts/staking_router.py | 8 ++++---- src/services/validator_state.py | 2 +- src/web3py/extensions/lido_validators.py | 5 ++++- tests/web3_extentions/test_middleware.py | 9 +++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/providers/execution/contracts/staking_router.py b/src/providers/execution/contracts/staking_router.py index ac45815a0..d3df8c78b 100644 --- a/src/providers/execution/contracts/staking_router.py +++ b/src/providers/execution/contracts/staking_router.py @@ -29,15 +29,15 @@ def get_staking_modules(self, block_identifier: BlockIdentifier = 'latest') -> l return response @lru_cache(maxsize=1) - @list_of_dataclasses(NodeOperator) - def get_all_node_operator_digests(self, module_id: int, block_identifier: BlockIdentifier = 'latest') -> list[NodeOperator]: + def get_all_node_operator_digests(self, module: StakingModule, block_identifier: BlockIdentifier = 'latest') -> list[NodeOperator]: """ Returns node operator digest for each node operator in lido protocol """ - response = self.functions.getAllNodeOperatorDigests(module_id).call(block_identifier=block_identifier) + response = self.functions.getAllNodeOperatorDigests(module.id).call(block_identifier=block_identifier) + response = [NodeOperator.from_response(no, module) for no in response] logger.info({ - 'msg': 'Call `getAllNodeOperatorDigests({})`.'.format(module_id), + 'msg': 'Call `getAllNodeOperatorDigests({})`.'.format(module.id), 'value': response, 'block_identifier': block_identifier.__repr__(), }) diff --git a/src/services/validator_state.py b/src/services/validator_state.py index f41bb20d4..840d08a24 100644 --- a/src/services/validator_state.py +++ b/src/services/validator_state.py @@ -123,7 +123,7 @@ def get_operators_with_last_exited_validator_indexes(self, blockstamp: BlockStam staking_modules = self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash) for module in staking_modules: - node_operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module.id, blockstamp.block_hash) + node_operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module, blockstamp.block_hash) last_requested_ids = self.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices( module.id, diff --git a/src/web3py/extensions/lido_validators.py b/src/web3py/extensions/lido_validators.py index 2aaf13dcb..2a6a1b9f5 100644 --- a/src/web3py/extensions/lido_validators.py +++ b/src/web3py/extensions/lido_validators.py @@ -43,6 +43,9 @@ class StakingModule: # number of exited validators exited_validators_count: int + def __hash__(self): + return hash(self.id) + @dataclass class NodeOperator(Nested): @@ -171,7 +174,7 @@ def get_lido_node_operators(self, blockstamp: BlockStamp) -> list[NodeOperator]: result = [] for module in self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash): - operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module.id, blockstamp.block_hash) + operators = self.w3.lido_contracts.staking_router.get_all_node_operator_digests(module, blockstamp.block_hash) result.extend(operators) return result diff --git a/tests/web3_extentions/test_middleware.py b/tests/web3_extentions/test_middleware.py index 6c4fb0d54..abb409942 100644 --- a/tests/web3_extentions/test_middleware.py +++ b/tests/web3_extentions/test_middleware.py @@ -1,6 +1,7 @@ import pytest from requests import HTTPError from web3 import Web3, HTTPProvider +from web3.exceptions import MethodUnavailable from src.metrics.prometheus.basic import EL_REQUESTS_DURATION from src.variables import EXECUTION_CLIENT_URI @@ -42,7 +43,7 @@ def test_success(provider, web3): 'call_to': '', 'code': '0', 'endpoint': 'eth_blockNumber', - 'le': '0.005', + 'le': '0.01', } @@ -56,12 +57,12 @@ def test_fail_with_status_code(provider, web3): 'call_to': '', 'code': '404', 'endpoint': 'eth_blockNumber', - 'le': '0.005', + 'le': '0.01', } def test_fail_with_body_error(provider, web3): - with pytest.raises(ValueError): + with pytest.raises(MethodUnavailable): web3.eth.coinbase labels = _get_requests_labels() - assert labels == {'call_method': '', 'call_to': '', 'code': '-32000', 'endpoint': 'eth_coinbase', 'le': '0.005'} + assert labels == {'call_method': '', 'call_to': '', 'code': '-32601', 'endpoint': 'eth_coinbase', 'le': '0.01'} From efba4746b752ccc11eec831ab824f46ba9714eae Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 10:52:43 +0200 Subject: [PATCH 07/21] run black --- .../modules/accounting/bunker/test_bunker.py | 2 +- .../accounting/test_accounting_module.py | 8 +++- .../accounting/test_safe_border_unit.py | 24 +++++++----- .../accounting/test_validator_state.py | 38 ++++++++++--------- .../accounting/test_withdrawal_integration.py | 8 +++- .../accounting/test_withdrawal_unit.py | 8 +++- tests/modules/ejector/test_ejector.py | 8 +++- .../ejector/test_exit_order_iterator.py | 8 ++-- tests/services/test_safe_border.py | 4 +- 9 files changed, 66 insertions(+), 42 deletions(-) diff --git a/tests/modules/accounting/bunker/test_bunker.py b/tests/modules/accounting/bunker/test_bunker.py index d444d7732..a3340d1c2 100644 --- a/tests/modules/accounting/bunker/test_bunker.py +++ b/tests/modules/accounting/bunker/test_bunker.py @@ -203,7 +203,7 @@ def test_get_cl_rebase_for_frame( simulated_post_total_pooled_ether, expected_rebase, ): - bunker.w3.lido_contracts.lido.total_supply = Mock(return_value=15 * 10 ** 18) + bunker.w3.lido_contracts.lido.total_supply = Mock(return_value=15 * 10**18) blockstamp = simple_ref_blockstamp(0) simulated_cl_rebase = LidoReportRebase( diff --git a/tests/modules/accounting/test_accounting_module.py b/tests/modules/accounting/test_accounting_module.py index 2b25bff92..1f24b08bb 100644 --- a/tests/modules/accounting/test_accounting_module.py +++ b/tests/modules/accounting/test_accounting_module.py @@ -349,13 +349,17 @@ def test_is_main_data_submitted( accounting: Accounting, bs: BlockStamp, ): - accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock(return_value=Mock(main_data_submitted=False)) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock( + return_value=Mock(main_data_submitted=False) + ) assert accounting.is_main_data_submitted(bs) is False, "is_main_data_submitted returned unexpected value" accounting.w3.lido_contracts.accounting_oracle.get_processing_state.assert_called_once_with(bs.block_hash) accounting.w3.lido_contracts.accounting_oracle.get_processing_state.reset_mock() - accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock(return_value=Mock(main_data_submitted=True)) + accounting.w3.lido_contracts.accounting_oracle.get_processing_state = Mock( + return_value=Mock(main_data_submitted=True) + ) assert accounting.is_main_data_submitted(bs) is True, "is_main_data_submitted returned unexpected value" accounting.w3.lido_contracts.accounting_oracle.get_processing_state.assert_called_once_with(bs.block_hash) diff --git a/tests/modules/accounting/test_safe_border_unit.py b/tests/modules/accounting/test_safe_border_unit.py index 433fcc7fe..76400f31f 100644 --- a/tests/modules/accounting/test_safe_border_unit.py +++ b/tests/modules/accounting/test_safe_border_unit.py @@ -47,7 +47,9 @@ def safe_border( consensus_client, lido_validators, ): - web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock(return_value=OracleReportLimitsFactory.build()) + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock( + return_value=OracleReportLimitsFactory.build() + ) web3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift = Mock(return_value=100) return SafeBorder(web3, past_blockstamp, chain_config, frame_config) @@ -100,7 +102,9 @@ def test_get_negative_rebase_border_epoch_bunker_not_started_yet(safe_border, pa def test_get_negative_rebase_border_epoch_max(safe_border, past_blockstamp): ref_epoch = past_blockstamp.ref_slot // SLOTS_PER_EPOCH - max_negative_rebase_shift = safe_border.w3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift() + max_negative_rebase_shift = ( + safe_border.w3.lido_contracts.oracle_daemon_config.finalization_max_negative_rebase_epoch_shift() + ) test_epoch = ref_epoch - max_negative_rebase_shift - 1 safe_border._get_bunker_mode_start_timestamp = Mock(return_value=test_epoch * SLOTS_PER_EPOCH * SLOT_TIME) @@ -116,8 +120,8 @@ def test_get_associated_slashings_border_epoch(safe_border, past_blockstamp): test_epoch = ref_epoch - 100 safe_border._get_earliest_slashed_epoch_among_incomplete_slashings = Mock(return_value=test_epoch) assert ( - safe_border._get_associated_slashings_border_epoch() - == safe_border.round_epoch_by_frame(test_epoch) - safe_border.finalization_default_shift + safe_border._get_associated_slashings_border_epoch() + == safe_border.round_epoch_by_frame(test_epoch) - safe_border.finalization_default_shift ) @@ -140,7 +144,7 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_no_slashed_valida def test_get_earliest_slashed_epoch_among_incomplete_slashings_withdrawable_validators( - safe_border, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): withdrawable_epoch = past_blockstamp.ref_epoch - 10 validators = [create_validator_stub(100, withdrawable_epoch, True)] @@ -150,7 +154,7 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_withdrawable_vali def test_get_earliest_slashed_epoch_among_incomplete_slashings_unable_to_predict( - safe_border, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): non_withdrawable_epoch = past_blockstamp.ref_epoch + 10 validators = [ @@ -165,7 +169,7 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_unable_to_predict def test_get_earliest_slashed_epoch_among_incomplete_slashings_all_withdrawable( - safe_border, past_blockstamp, lido_validators + safe_border, past_blockstamp, lido_validators ): validators = [ create_validator_stub( @@ -198,7 +202,7 @@ def test_get_earliest_slashed_epoch_among_incomplete_slashings_predicted(safe_bo def test_get_earliest_slashed_epoch_among_incomplete_slashings_at_least_one_unpredictable_epoch( - safe_border, + safe_border, past_blockstamp, lido_validators, ): @@ -234,7 +238,9 @@ def test_get_last_finalized_withdrawal_request_slot(safe_border): timestamp = 1677230000 safe_border.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id = Mock(return_value=3) - safe_border.w3.lido_contracts.withdrawal_queue_nft.get_withdrawal_status = Mock(return_value=WithdrawalStatus(timestamp=timestamp)) + safe_border.w3.lido_contracts.withdrawal_queue_nft.get_withdrawal_status = Mock( + return_value=WithdrawalStatus(timestamp=timestamp) + ) slot = (timestamp - safe_border.chain_config.genesis_time) // safe_border.chain_config.seconds_per_slot epoch = slot // safe_border.chain_config.slots_per_epoch diff --git a/tests/modules/accounting/test_validator_state.py b/tests/modules/accounting/test_validator_state.py index 43e3de034..611f53ec3 100644 --- a/tests/modules/accounting/test_validator_state.py +++ b/tests/modules/accounting/test_validator_state.py @@ -109,28 +109,32 @@ def validator(index: int, exit_epoch: int, pubkey: HexStr, activation_epoch: int ), ) - web3.lido_validators.get_lido_validators_by_node_operators = Mock(return_value={ - (StakingModuleId(1), NodeOperatorId(0)): [ - validator(index=1, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x1'), # Stuck - validator(index=2, exit_epoch=30, pubkey='0x2'), - validator(index=3, exit_epoch=50, pubkey='0x3'), - validator(index=4, exit_epoch=TESTING_REF_EPOCH, pubkey='0x4'), - ], - (StakingModuleId(1), NodeOperatorId(1)): [ - validator(index=5, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x5', activation_epoch=290), # Stuck but newest - validator( - index=6, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x6', activation_epoch=282 - ), # Stuck in the same epoch - validator(index=7, exit_epoch=20, pubkey='0x7'), - validator(index=8, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x8'), - ], - }) + web3.lido_validators.get_lido_validators_by_node_operators = Mock( + return_value={ + (StakingModuleId(1), NodeOperatorId(0)): [ + validator(index=1, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x1'), # Stuck + validator(index=2, exit_epoch=30, pubkey='0x2'), + validator(index=3, exit_epoch=50, pubkey='0x3'), + validator(index=4, exit_epoch=TESTING_REF_EPOCH, pubkey='0x4'), + ], + (StakingModuleId(1), NodeOperatorId(1)): [ + validator(index=5, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x5', activation_epoch=290), # Stuck but newest + validator( + index=6, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x6', activation_epoch=282 + ), # Stuck in the same epoch + validator(index=7, exit_epoch=20, pubkey='0x7'), + validator(index=8, exit_epoch=FAR_FUTURE_EPOCH, pubkey='0x8'), + ], + } + ) @pytest.fixture def validator_state(web3, contracts, consensus_client, lido_validators): service = LidoValidatorStateService(web3) - service.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices = Mock(return_value=[3, 8]) + service.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices = Mock( + return_value=[3, 8] + ) return service diff --git a/tests/modules/accounting/test_withdrawal_integration.py b/tests/modules/accounting/test_withdrawal_integration.py index a5c9b422a..8c31233db 100644 --- a/tests/modules/accounting/test_withdrawal_integration.py +++ b/tests/modules/accounting/test_withdrawal_integration.py @@ -30,8 +30,12 @@ def test_happy_path(subject, past_blockstamp): withdrawal_vault_balance = subject.w3.lido_contracts.get_withdrawal_balance(past_blockstamp) el_rewards_vault_balance = subject.w3.lido_contracts.get_el_vault_balance(past_blockstamp) - expected_min_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id(past_blockstamp.block_hash) - expected_max_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_request_id(past_blockstamp.block_hash) + expected_min_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_finalized_request_id( + past_blockstamp.block_hash + ) + expected_max_withdrawal_id = subject.w3.lido_contracts.withdrawal_queue_nft.get_last_request_id( + past_blockstamp.block_hash + ) results = subject.get_finalization_batches( False, SHARE_RATE_PRECISION_E27, withdrawal_vault_balance, el_rewards_vault_balance diff --git a/tests/modules/accounting/test_withdrawal_unit.py b/tests/modules/accounting/test_withdrawal_unit.py index b24da0b0b..0b88ab79e 100644 --- a/tests/modules/accounting/test_withdrawal_unit.py +++ b/tests/modules/accounting/test_withdrawal_unit.py @@ -26,7 +26,9 @@ def past_blockstamp(web3, consensus_client): @pytest.fixture() def subject(web3, past_blockstamp, chain_config, frame_config, contracts, keys_api_client, consensus_client): - web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock(return_value=OracleReportLimitsFactory.build()) + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock( + return_value=OracleReportLimitsFactory.build() + ) return Withdrawal(web3, past_blockstamp, chain_config, frame_config) @@ -61,7 +63,9 @@ def test_returns_batch_if_there_are_finalizable_requests(subject: Withdrawal): def test_calculate_finalization_batches(subject: Withdrawal, past_blockstamp): state_initial = BatchState(remaining_eth_budget=100, finished=False, batches=[1] + [0] * 35, batches_length=1) state_final = BatchState(remaining_eth_budget=100, finished=True, batches=[2] + [0] * 35, batches_length=2) - subject.w3.lido_contracts.withdrawal_queue_nft.calculate_finalization_batches = Mock(side_effect=[state_initial, state_final]) + subject.w3.lido_contracts.withdrawal_queue_nft.calculate_finalization_batches = Mock( + side_effect=[state_initial, state_final] + ) subject.w3.lido_contracts.withdrawal_queue_nft.max_batches_length = Mock(return_value=36) result = subject._calculate_finalization_batches(1, SHARE_RATE_PRECISION_E27, past_blockstamp.block_timestamp) diff --git a/tests/modules/ejector/test_ejector.py b/tests/modules/ejector/test_ejector.py index 2613b1c10..3ccc8dc8f 100644 --- a/tests/modules/ejector/test_ejector.py +++ b/tests/modules/ejector/test_ejector.py @@ -177,9 +177,13 @@ def test_compute_activation_exit_epoch( @pytest.mark.unit def test_is_main_data_submitted(ejector: Ejector, blockstamp: BlockStamp) -> None: - ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state = Mock(return_value=Mock(data_submitted=True)) + ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state = Mock( + return_value=Mock(data_submitted=True) + ) assert ejector.is_main_data_submitted(blockstamp) is True, "Unexpected is_main_data_submitted result" - ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state.assert_called_once_with(blockstamp.block_hash) + ejector.w3.lido_contracts.validators_exit_bus_oracle.get_processing_state.assert_called_once_with( + blockstamp.block_hash + ) @pytest.mark.unit diff --git a/tests/modules/ejector/test_exit_order_iterator.py b/tests/modules/ejector/test_exit_order_iterator.py index 4c1a31f3d..95292af88 100644 --- a/tests/modules/ejector/test_exit_order_iterator.py +++ b/tests/modules/ejector/test_exit_order_iterator.py @@ -152,8 +152,8 @@ class MockedExitOrderIteratorStateService(ExitOrderIteratorStateService): @pytest.mark.unit def test_exit_order_iterator_iter(web3, lido_validators, contracts, mock_exit_order_iterator_state_service): - web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( - Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock( + return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100) ) iterator = ExitOrderIterator(web3, ReferenceBlockStampFactory.build(), ChainConfigFactory.build()) @@ -173,8 +173,8 @@ def test_exit_order_iterator_iter(web3, lido_validators, contracts, mock_exit_or @pytest.mark.unit def test_exit_order_iterator_next(web3, lido_validators, contracts, mock_exit_order_iterator_state_service): - web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = ( - Mock(return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100)) + web3.lido_contracts.oracle_report_sanity_checker.get_oracle_report_limits = Mock( + return_value=OracleReportLimitsFactory.build(max_validator_exit_requests_per_report=100) ) iterator = ExitOrderIterator(web3, ReferenceBlockStampFactory.build(), ChainConfigFactory.build()) diff --git a/tests/services/test_safe_border.py b/tests/services/test_safe_border.py index d5bbb491a..5d8dce52c 100644 --- a/tests/services/test_safe_border.py +++ b/tests/services/test_safe_border.py @@ -128,9 +128,7 @@ def test_find_earliest_slashed_epoch_rounded_to_frame( SafeBorder._retrieve_constants = Mock() SafeBorder._get_negative_rebase_border_epoch = Mock() SafeBorder._get_associated_slashings_border_epoch = Mock() - SafeBorder._get_last_finalized_withdrawal_request_slot = Mock( - return_value=last_finalized_withdrawal_request_slot - ) + SafeBorder._get_last_finalized_withdrawal_request_slot = Mock(return_value=last_finalized_withdrawal_request_slot) SafeBorder._slashings_in_frame = Mock(return_value=slashings_in_frame) web3Mock = Mock() From 8f4452b9548f0014c67a91d247eef1108651d31a Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 11:16:06 +0200 Subject: [PATCH 08/21] fix linters --- pyproject.toml | 1 + src/modules/ejector/ejector.py | 2 +- src/modules/submodules/consensus.py | 5 ++-- src/providers/execution/base_interface.py | 4 ++-- .../execution/contracts/accounting_oracle.py | 4 ++-- .../execution/contracts/base_oracle.py | 16 ++++++------- src/providers/execution/contracts/burner.py | 2 +- .../execution/contracts/exit_bus_oracle.py | 8 +++---- .../execution/contracts/hash_consensus.py | 17 ++++++++------ src/providers/execution/contracts/lido.py | 12 +++++----- .../execution/contracts/lido_locator.py | 20 ++++++++-------- .../contracts/oracle_daemon_config.py | 18 +++++++-------- .../contracts/oracle_report_sanity_checker.py | 2 +- .../execution/contracts/staking_router.py | 6 ++--- .../contracts/withdrawal_queue_nft.py | 23 +++++++++---------- src/services/safe_border.py | 4 +--- src/services/validator_state.py | 5 +--- src/web3py/types.py | 2 -- 18 files changed, 73 insertions(+), 78 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 38330057c..3c4fcc291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ branch = true [tool.pylint.format] max-line-length = "120" +min-similarity-lines=6 [tool.pylint."messages control"] diff --git a/src/modules/ejector/ejector.py b/src/modules/ejector/ejector.py index d4ad01362..c22453c65 100644 --- a/src/modules/ejector/ejector.py +++ b/src/modules/ejector/ejector.py @@ -20,7 +20,7 @@ ) from src.metrics.prometheus.duration_meter import duration_meter from src.modules.ejector.data_encode import encode_data -from src.modules.ejector.types import EjectorProcessingState, ReportData +from src.modules.ejector.types import ReportData from src.modules.submodules.consensus import ConsensusModule from src.modules.submodules.oracle_module import BaseModule, ModuleExecuteDelay from src.providers.consensus.types import Validator diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index 85a5f45c1..6db6c8e38 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -4,9 +4,8 @@ from typing import Optional, cast from eth_abi import encode -from eth_typing import ChecksumAddress, Hash32 +from eth_typing import Hash32 from hexbytes import HexBytes -from web3.contract import AsyncContract, Contract from src import variables from src.metrics.prometheus.basic import ORACLE_SLOT_NUMBER, ORACLE_BLOCK_NUMBER, GENESIS_TIME, ACCOUNT_BALANCE @@ -21,13 +20,13 @@ ) from src.modules.submodules.exceptions import IsNotMemberException, IncompatibleContractVersion from src.modules.submodules.types import ChainConfig, MemberInfo, ZERO_HASH, CurrentFrame, FrameConfig -from src.utils.abi import named_tuple_to_dataclass from src.utils.blockstamp import build_blockstamp from src.utils.web3converter import Web3Converter from src.utils.slot import get_reference_blockstamp from src.utils.cache import global_lru_cache as lru_cache from src.web3py.types import Web3 + logger = logging.getLogger(__name__) diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index 5c0a9ba05..873a627dd 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -15,9 +15,9 @@ def load_abi(abi_file: str) -> dict: return json.load(abi_json) @classmethod - def factory(cls, web3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': if cls.abi_path is None: raise AttributeError(f'abi_path attribute is missing in {cls.__name__} class') kwargs['abi'] = cls.load_abi(cls.abi_path) - return super().factory(web3, class_name, **kwargs) + return super().factory(w3, class_name, **kwargs) diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py index 464d154a2..8f13b875d 100644 --- a/src/providers/execution/contracts/accounting_oracle.py +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -24,7 +24,7 @@ def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> logger.info({ 'msg': 'Call `getProcessingState()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -42,5 +42,5 @@ def submit_report_extra_data_list(self, extra_data: bytes) -> TxParams: Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. """ tx = self.functions.submitReportExtraDataList(extra_data) - logger.info({'msg': 'Build `submitReportExtraDataList({})` tx.'.format(extra_data)}) + logger.info({'msg': f'Build `submitReportExtraDataList({extra_data})` tx.'}) return tx diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py index 406dbdb23..0540312c1 100644 --- a/src/providers/execution/contracts/base_oracle.py +++ b/src/providers/execution/contracts/base_oracle.py @@ -22,7 +22,7 @@ def get_consensus_contract(self, block_identifier: BlockIdentifier = 'latest') - logger.info({ 'msg': 'Call `getConsensusContract()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -35,7 +35,7 @@ def submit_data_role(self, block_identifier: BlockIdentifier = 'latest') -> Hash logger.info({ 'msg': 'Call `SUBMIT_DATA_ROLE()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -46,9 +46,9 @@ def has_role(self, role: Hash32, address: ChecksumAddress, block_identifier: Blo """ response = self.functions.hasRole(role, address).call(block_identifier=block_identifier) logger.info({ - 'msg': 'Call `hasRole({}, {})`.'.format(role, address), + 'msg': f'Call `hasRole({role}, {address})`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -61,7 +61,7 @@ def get_contract_version(self, block_identifier: BlockIdentifier = 'latest') -> logger.info({ 'msg': 'Call `getContractVersion().', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -76,7 +76,7 @@ def get_consensus_version(self, block_identifier: BlockIdentifier = 'latest') -> logger.info({ 'msg': 'Call `getConsensusVersion().', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -97,7 +97,7 @@ def submit_report_data(self, report, contract_version: int) -> TxParams: """ tx = self.functions.submitReportData(report.as_tuple(), contract_version) logger.info({ - 'msg': 'Build `submitReport({}, {}) tx.'.format(report.as_tuple(), contract_version) + 'msg': f'Build `submitReport({report.as_tuple()}, {contract_version}) tx.' }) return tx @@ -111,6 +111,6 @@ def get_last_processing_ref_slot(self, block_identifier: BlockIdentifier = 'late logger.info({ 'msg': 'Call `getLastProcessingRefSlot().', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return SlotNumber(response) diff --git a/src/providers/execution/contracts/burner.py b/src/providers/execution/contracts/burner.py index fdb9579fb..6a14d98a9 100644 --- a/src/providers/execution/contracts/burner.py +++ b/src/providers/execution/contracts/burner.py @@ -25,6 +25,6 @@ def get_shares_requested_to_burn(self, block_identifier: BlockIdentifier = 'late logger.info({ 'msg': 'Call `totalSupply()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/exit_bus_oracle.py b/src/providers/execution/contracts/exit_bus_oracle.py index e876da934..1c4f0cc93 100644 --- a/src/providers/execution/contracts/exit_bus_oracle.py +++ b/src/providers/execution/contracts/exit_bus_oracle.py @@ -24,7 +24,7 @@ def is_paused(self, block_identifier: BlockIdentifier = 'latest') -> bool: logger.info({ 'msg': 'Call `isPaused()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -38,7 +38,7 @@ def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> logger.info({ 'msg': 'Call `getProcessingState()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -60,8 +60,8 @@ def get_last_requested_validator_indices( ).call(block_identifier=block_identifier) logger.info({ - 'msg': 'Call `getLastRequestedValidatorIndices({}, {})`.'.format(module_id, node_operators_ids_in_module), + 'msg': 'Call `getLastRequestedValidatorIndices({module_id}, {node_operators_ids_in_module})`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py index ef1dc9ef7..fe94e4a91 100644 --- a/src/providers/execution/contracts/hash_consensus.py +++ b/src/providers/execution/contracts/hash_consensus.py @@ -26,7 +26,7 @@ def get_members(self, block_identifier: BlockIdentifier = 'latest') -> tuple[lis logger.info({ 'msg': 'Call `getMembers()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -43,7 +43,7 @@ def get_chain_config(self, block_identifier: BlockIdentifier = 'latest') -> Chai logger.info({ 'msg': 'Call `getChainConfig()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -67,7 +67,7 @@ def get_current_frame(self, block_identifier: BlockIdentifier = 'latest') -> Cur logger.info({ 'msg': 'Call `getCurrentFrame()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -87,7 +87,7 @@ def get_frame_config(self, block_identifier: BlockIdentifier = 'latest') -> Fram logger.info({ 'msg': 'Call `getFrameConfig()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -104,7 +104,7 @@ def get_consensus_state_for_member(self, address: ChecksumAddress, block_identif logger.info({ 'msg': 'Call `getFrameConfig()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -125,8 +125,11 @@ def submit_report(self, ref_slot: int, report_hash: Hash32, consensus_version: i tx = self.functions.submitReport(ref_slot, report_hash, consensus_version) logger.info({ - 'msg': 'Build `submitReport({}, {}, {})`.'.format(ref_slot, report_hash, consensus_version), + 'msg': 'Build `submitReport({}, {}, {})`.'.format( # pylint: disable=consider-using-f-string + ref_slot, + report_hash, + consensus_version, + ), }) return tx - diff --git a/src/providers/execution/contracts/lido.py b/src/providers/execution/contracts/lido.py index 5a97cb97b..8d8e0535e 100644 --- a/src/providers/execution/contracts/lido.py +++ b/src/providers/execution/contracts/lido.py @@ -1,7 +1,7 @@ import logging from eth_typing import ChecksumAddress -from web3.types import Gwei, Wei, BlockIdentifier +from web3.types import Wei, BlockIdentifier from src.modules.accounting.types import LidoReportRebase from src.providers.execution.base_interface import ContractInterface @@ -52,7 +52,7 @@ def handle_oracle_report( response = LidoReportRebase(*response) logger.info({ - 'msg': 'Call `handleOracleReport({}, {}, {}, {}, {}, {}, {}, {}, {})`.'.format( + 'msg': 'Call `handleOracleReport({}, {}, {}, {}, {}, {}, {}, {}, {})`.'.format( # pylint: disable=consider-using-f-string timestamp, time_elapsed, validators_count, @@ -64,7 +64,7 @@ def handle_oracle_report( 0, ), 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -80,7 +80,7 @@ def get_buffered_ether(self, block_identifier: BlockIdentifier = 'latest') -> We logger.info({ 'msg': 'Call `getBufferedEther()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return Wei(response) @@ -96,6 +96,6 @@ def total_supply(self, block_identifier: BlockIdentifier = 'latest') -> Wei: logger.info({ 'msg': 'Call `totalSupply()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) - return Wei(response) \ No newline at end of file + return Wei(response) diff --git a/src/providers/execution/contracts/lido_locator.py b/src/providers/execution/contracts/lido_locator.py index d28c79041..99cd2d112 100644 --- a/src/providers/execution/contracts/lido_locator.py +++ b/src/providers/execution/contracts/lido_locator.py @@ -20,7 +20,7 @@ def lido(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddress: logger.info({ 'msg': 'Call `lido()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -31,7 +31,7 @@ def accounting_oracle(self, block_identifier: BlockIdentifier = 'latest') -> Che logger.info({ 'msg': 'Call `accountingOracle()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -42,7 +42,7 @@ def staking_router(self, block_identifier: BlockIdentifier = 'latest') -> Checks logger.info({ 'msg': 'Call `stakingRouter()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -53,7 +53,7 @@ def validator_exit_bus_oracle(self, block_identifier: BlockIdentifier = 'latest' logger.info({ 'msg': 'Call `validatorsExitBusOracle()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -64,7 +64,7 @@ def withdrawal_queue(self, block_identifier: BlockIdentifier = 'latest') -> Chec logger.info({ 'msg': 'Call `withdrawalQueue()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -75,7 +75,7 @@ def oracle_report_sanity_checker(self, block_identifier: BlockIdentifier = 'late logger.info({ 'msg': 'Call `oracleReportSanityChecker()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -86,7 +86,7 @@ def oracle_daemon_config(self, block_identifier: BlockIdentifier = 'latest') -> logger.info({ 'msg': 'Call `oracleDaemonConfig()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -97,7 +97,7 @@ def burner(self, block_identifier: BlockIdentifier = 'latest') -> ChecksumAddres logger.info({ 'msg': 'Call `burner()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -108,7 +108,7 @@ def withdrawal_vault(self, block_identifier: BlockIdentifier = 'latest') -> Chec logger.info({ 'msg': 'Call `withdrawalVault()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -119,6 +119,6 @@ def el_rewards_vault(self, block_identifier: BlockIdentifier = 'latest') -> Chec logger.info({ 'msg': 'Call `elRewardsVault()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/oracle_daemon_config.py b/src/providers/execution/contracts/oracle_daemon_config.py index eb88c3606..dbce1cf4d 100644 --- a/src/providers/execution/contracts/oracle_daemon_config.py +++ b/src/providers/execution/contracts/oracle_daemon_config.py @@ -21,7 +21,7 @@ def normalized_cl_reward_per_epoch(self, block_identifier: BlockIdentifier = 'la logger.info({ 'msg': 'Call `NORMALIZED_CL_REWARD_PER_EPOCH()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -33,7 +33,7 @@ def normalized_cl_reward_mistake_rate_bp(self, block_identifier: BlockIdentifier logger.info({ 'msg': 'Call `NORMALIZED_CL_REWARD_MISTAKE_RATE_BP()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -45,7 +45,7 @@ def rebase_check_nearest_epoch_distance(self, block_identifier: BlockIdentifier logger.info({ 'msg': 'Call `REBASE_CHECK_NEAREST_EPOCH_DISTANCE()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -57,7 +57,7 @@ def rebase_check_distant_epoch_distance(self, block_identifier: BlockIdentifier logger.info({ 'msg': 'Call `REBASE_CHECK_DISTANT_EPOCH_DISTANCE()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -69,7 +69,7 @@ def node_operator_network_penetration_threshold_bp(self, block_identifier: Block logger.info({ 'msg': 'Call `NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -81,7 +81,7 @@ def prediction_duration_in_slots(self, block_identifier: BlockIdentifier = 'late logger.info({ 'msg': 'Call `PREDICTION_DURATION_IN_SLOTS()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -94,7 +94,7 @@ def finalization_max_negative_rebase_epoch_shift(self, block_identifier: BlockId logger.info({ 'msg': 'Call `FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -105,7 +105,7 @@ def validator_delayed_timeout_in_slots(self, block_identifier: BlockIdentifier = logger.info({ 'msg': 'Call `VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -116,6 +116,6 @@ def validator_delinquent_timeout_in_slots(self, block_identifier: BlockIdentifie logger.info({ 'msg': 'Call `VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/oracle_report_sanity_checker.py b/src/providers/execution/contracts/oracle_report_sanity_checker.py index c2231e937..9541a5221 100644 --- a/src/providers/execution/contracts/oracle_report_sanity_checker.py +++ b/src/providers/execution/contracts/oracle_report_sanity_checker.py @@ -25,6 +25,6 @@ def get_oracle_report_limits(self, block_identifier: BlockIdentifier = 'latest') logger.info({ 'msg': 'Call `getOracleReportLimits()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/staking_router.py b/src/providers/execution/contracts/staking_router.py index d3df8c78b..1719b5366 100644 --- a/src/providers/execution/contracts/staking_router.py +++ b/src/providers/execution/contracts/staking_router.py @@ -24,7 +24,7 @@ def get_staking_modules(self, block_identifier: BlockIdentifier = 'latest') -> l logger.info({ 'msg': 'Call `getStakingModules()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -37,8 +37,8 @@ def get_all_node_operator_digests(self, module: StakingModule, block_identifier: response = [NodeOperator.from_response(no, module) for no in response] logger.info({ - 'msg': 'Call `getAllNodeOperatorDigests({})`.'.format(module.id), + 'msg': f'Call `getAllNodeOperatorDigests({module.id})`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/providers/execution/contracts/withdrawal_queue_nft.py b/src/providers/execution/contracts/withdrawal_queue_nft.py index 3246a0182..28b0c87e9 100644 --- a/src/providers/execution/contracts/withdrawal_queue_nft.py +++ b/src/providers/execution/contracts/withdrawal_queue_nft.py @@ -22,7 +22,7 @@ def unfinalized_steth(self, block_identifier: BlockIdentifier = 'latest') -> Wei logger.info({ 'msg': 'Call `unfinalizedStETH()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return Wei(response) @@ -38,7 +38,7 @@ def bunker_mode_since_timestamp(self, block_identifier: BlockIdentifier = 'lates logger.info({ 'msg': 'Call `bunkerModeSinceTimestamp()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -53,7 +53,7 @@ def get_last_finalized_request_id(self, block_identifier: BlockIdentifier = 'lat logger.info({ 'msg': 'Call `getLastFinalizedRequestId()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -67,9 +67,9 @@ def get_withdrawal_status(self, request_id: int, block_identifier: BlockIdentifi response = named_tuple_to_dataclass(response, WithdrawalRequestStatus) logger.info({ - 'msg': 'Call `getWithdrawalStatus({})`.'.format([request_id]), + 'msg': f'Call `getWithdrawalStatus({[request_id]})`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -84,21 +84,20 @@ def get_last_request_id(self, block_identifier: BlockIdentifier = 'latest') -> i logger.info({ 'msg': 'Call `getLastRequestId()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @lru_cache(maxsize=1) def is_paused(self, block_identifier: BlockIdentifier = 'latest') -> bool: """ - Returns whether the contract is paused + Returns whether the withdrawal queue is paused """ response = self.functions.isPaused().call(block_identifier=block_identifier) - logger.info({ 'msg': 'Call `isPaused()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -112,7 +111,7 @@ def max_batches_length(self, block_identifier: BlockIdentifier = 'latest') -> in logger.info({ 'msg': 'Call `MAX_BATCHES_LENGTH()`.', 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response @@ -138,13 +137,13 @@ def calculate_finalization_batches( ).call(block_identifier=block_identifier) logger.info({ - 'msg': 'Call `calculateFinalizationBatches({}, {}, {}, {})`.'.format( + 'msg': 'Call `calculateFinalizationBatches({}, {}, {}, {})`.'.format( # pylint: disable=consider-using-f-string share_rate, timestamp, max_batch_request_count, batch_state, ), 'value': response, - 'block_identifier': block_identifier.__repr__(), + 'block_identifier': repr(block_identifier), }) return response diff --git a/src/services/safe_border.py b/src/services/safe_border.py index 06d30c83b..cfff96e9d 100644 --- a/src/services/safe_border.py +++ b/src/services/safe_border.py @@ -1,14 +1,12 @@ import math -from typing import Any, Iterable, Optional +from typing import Iterable, Optional from eth_typing import HexStr from src.constants import EPOCHS_PER_SLASHINGS_VECTOR, MIN_VALIDATOR_WITHDRAWABILITY_DELAY from src.metrics.prometheus.duration_meter import duration_meter from src.modules.submodules.consensus import ChainConfig, FrameConfig -from src.modules.accounting.types import OracleReportLimits from src.utils.web3converter import Web3Converter -from src.utils.abi import named_tuple_to_dataclass from src.types import EpochNumber, FrameNumber, ReferenceBlockStamp, SlotNumber from src.web3py.extensions.lido_validators import Validator from src.web3py.types import Web3 diff --git a/src/services/validator_state.py b/src/services/validator_state.py index 840d08a24..31a0d024b 100644 --- a/src/services/validator_state.py +++ b/src/services/validator_state.py @@ -1,7 +1,7 @@ import logging from copy import deepcopy from functools import reduce -from typing import Sequence, Iterable +from typing import Iterable from eth_typing import HexStr @@ -12,10 +12,8 @@ ACCOUNTING_DELAYED_VALIDATORS, ) from src.modules.accounting.extra_data import ExtraDataService, ExtraData -from src.modules.accounting.types import OracleReportLimits from src.modules.submodules.types import ChainConfig from src.types import BlockStamp, ReferenceBlockStamp, EpochNumber -from src.utils.abi import named_tuple_to_dataclass from src.utils.events import get_events_in_past from src.utils.types import bytes_to_hex_str from src.utils.validator_state import is_exited_validator, is_validator_eligible_to_exit, is_on_exit @@ -23,7 +21,6 @@ from src.web3py.extensions.lido_validators import ( NodeOperatorGlobalIndex, LidoValidator, - StakingModule, ) from src.web3py.types import Web3 diff --git a/src/web3py/types.py b/src/web3py/types.py index 554471f16..3bccee23d 100644 --- a/src/web3py/types.py +++ b/src/web3py/types.py @@ -1,5 +1,3 @@ -from typing import NewType - from web3 import Web3 as _Web3 From c188993c41602de426a71cda987a82522f0ea172 Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 13:14:46 +0200 Subject: [PATCH 09/21] add blockhash --- src/modules/accounting/accounting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/accounting/accounting.py b/src/modules/accounting/accounting.py index ff1c9e45e..ef891f976 100644 --- a/src/modules/accounting/accounting.py +++ b/src/modules/accounting/accounting.py @@ -263,6 +263,7 @@ def simulate_rebase_after_report( el_rewards, # _elRewardsVaultBalance self.get_shares_to_burn(blockstamp), # _sharesRequestedToBurn self.w3.lido_contracts.accounting_oracle.address, + blockstamp.block_hash, ) def get_shares_to_burn(self, blockstamp: BlockStamp) -> int: From 7f28e4786025a01c9bdcc185ad685b00e358ecc0 Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 16:41:00 +0200 Subject: [PATCH 10/21] fix tests --- src/modules/accounting/accounting.py | 2 +- tests/modules/ejector/test_ejector.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/accounting/accounting.py b/src/modules/accounting/accounting.py index ef891f976..b3c38f75b 100644 --- a/src/modules/accounting/accounting.py +++ b/src/modules/accounting/accounting.py @@ -172,7 +172,7 @@ def _get_newly_exited_validators_by_modules( Calculate exited validators count in all modules. Exclude modules without changes from the report. """ - staking_modules = self.w3.lido_validators.get_staking_modules(blockstamp) + staking_modules = self.w3.lido_contracts.staking_router.get_staking_modules(blockstamp.block_hash) exited_validators = self.lido_validator_state_service.get_exited_lido_validators(blockstamp) return self.get_updated_modules_stats(staking_modules, exited_validators) diff --git a/tests/modules/ejector/test_ejector.py b/tests/modules/ejector/test_ejector.py index 3ccc8dc8f..ab1f0f82a 100644 --- a/tests/modules/ejector/test_ejector.py +++ b/tests/modules/ejector/test_ejector.py @@ -5,8 +5,9 @@ from src.constants import MAX_EFFECTIVE_BALANCE from src.modules.ejector import ejector as ejector_module -from src.modules.ejector.ejector import Ejector, EjectorProcessingState +from src.modules.ejector.ejector import Ejector from src.modules.ejector.ejector import logger as ejector_logger +from src.modules.ejector.types import EjectorProcessingState from src.modules.submodules.oracle_module import ModuleExecuteDelay from src.modules.submodules.types import ChainConfig from src.types import BlockStamp, ReferenceBlockStamp From dcb2476e80300798d2e911da92bc70bd670f4b03 Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 16:54:30 +0200 Subject: [PATCH 11/21] fix mypy --- src/modules/submodules/consensus.py | 8 ++++---- src/providers/execution/base_interface.py | 6 +++--- .../execution/contracts/accounting_oracle.py | 11 ++++++----- src/providers/execution/contracts/base_oracle.py | 7 ++++--- src/providers/execution/contracts/hash_consensus.py | 10 +++++----- src/services/validator_state.py | 2 +- tests/modules/accounting/test_accounting_module.py | 4 ++-- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index 6db6c8e38..d78add534 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -252,7 +252,7 @@ def process_report(self, blockstamp: ReferenceBlockStamp) -> None: # Even if report hash transaction was failed we have to check if we can report data for current frame self._process_report_data(blockstamp, report_data, report_hash) - def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: HexBytes) -> None: + def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: bytes) -> None: latest_blockstamp, member_info = self._get_latest_data() if not member_info.is_report_member: @@ -278,7 +278,7 @@ def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: Hex self._send_report_hash(blockstamp, report_hash, self.CONSENSUS_VERSION) return None - def _process_report_data(self, blockstamp: ReferenceBlockStamp, report_data: tuple, report_hash: HexBytes): + def _process_report_data(self, blockstamp: ReferenceBlockStamp, report_data: tuple, report_hash: bytes): latest_blockstamp, member_info = self._get_latest_data() if member_info.current_frame_consensus_report == ZERO_HASH: @@ -342,7 +342,7 @@ def _get_latest_data(self) -> tuple[BlockStamp, MemberInfo]: return latest_blockstamp, member_info - def _encode_data_hash(self, report_data: tuple): + def _encode_data_hash(self, report_data: tuple) -> bytes: # The Accounting Oracle and Ejector Bus has same named method to report data report_function_name = 'submitReportData' @@ -360,7 +360,7 @@ def _encode_data_hash(self, report_data: tuple): report_hash = self.w3.keccak(encoded) return report_hash - def _send_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: Hash32, consensus_version: int): + def _send_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: bytes, consensus_version: int): consensus_contract = self._get_consensus_contract(blockstamp) tx = consensus_contract.submit_report(blockstamp.ref_slot, report_hash, consensus_version) diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index 873a627dd..2f02b134a 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -1,5 +1,5 @@ import json -from typing import Optional, Any +from typing import Optional, Any, Type from web3 import Web3 @@ -7,7 +7,7 @@ class ContractInterface(Contract): - abi_path: str = None + abi_path: str @staticmethod def load_abi(abi_file: str) -> dict: @@ -15,7 +15,7 @@ def load_abi(abi_file: str) -> dict: return json.load(abi_json) @classmethod - def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> 'Contract': + def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> Type['ContractInterface']: if cls.abi_path is None: raise AttributeError(f'abi_path attribute is missing in {cls.__name__} class') diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py index 8f13b875d..7331d10ad 100644 --- a/src/providers/execution/contracts/accounting_oracle.py +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -1,7 +1,8 @@ import logging from functools import lru_cache -from web3.types import TxParams, BlockIdentifier +from web3.contract.contract import ContractFunction +from web3.types import BlockIdentifier from src.modules.accounting.types import AccountingProcessingState from src.providers.execution.contracts.base_oracle import BaseOracleContract @@ -19,7 +20,7 @@ def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> """ Returns data processing state for the current reporting frame. """ - response = self.functions.getProcessingState().call(block_identifier) + response = self.functions.getProcessingState().call(block_identifier=block_identifier) response = named_tuple_to_dataclass(response, AccountingProcessingState) logger.info({ 'msg': 'Call `getProcessingState()`.', @@ -28,7 +29,7 @@ def get_processing_state(self, block_identifier: BlockIdentifier = 'latest') -> }) return response - def submit_report_extra_data_empty(self) -> TxParams: + def submit_report_extra_data_empty(self) -> ContractFunction: """ Triggers the processing required when no extra data is present in the report, i.e. when extra data format equals EXTRA_DATA_FORMAT_EMPTY. @@ -37,10 +38,10 @@ def submit_report_extra_data_empty(self) -> TxParams: logger.info({'msg': 'Build `submitReportExtraDataEmpty()` tx.'}) return tx - def submit_report_extra_data_list(self, extra_data: bytes) -> TxParams: + def submit_report_extra_data_list(self, extra_data: bytes) -> ContractFunction: """ Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. """ tx = self.functions.submitReportExtraDataList(extra_data) - logger.info({'msg': f'Build `submitReportExtraDataList({extra_data})` tx.'}) + logger.info({'msg': f'Build `submitReportExtraDataList({str(extra_data)})` tx.'}) return tx diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py index 0540312c1..97136288b 100644 --- a/src/providers/execution/contracts/base_oracle.py +++ b/src/providers/execution/contracts/base_oracle.py @@ -2,7 +2,8 @@ from functools import lru_cache from eth_typing import ChecksumAddress, Hash32 -from web3.types import TxParams, BlockIdentifier +from web3.contract.contract import ContractFunction +from web3.types import BlockIdentifier from src.providers.execution.base_interface import ContractInterface from src.types import SlotNumber @@ -46,7 +47,7 @@ def has_role(self, role: Hash32, address: ChecksumAddress, block_identifier: Blo """ response = self.functions.hasRole(role, address).call(block_identifier=block_identifier) logger.info({ - 'msg': f'Call `hasRole({role}, {address})`.', + 'msg': f'Call `hasRole({str(role)}, {address})`.', 'value': response, 'block_identifier': repr(block_identifier), }) @@ -80,7 +81,7 @@ def get_consensus_version(self, block_identifier: BlockIdentifier = 'latest') -> }) return response - def submit_report_data(self, report, contract_version: int) -> TxParams: + def submit_report_data(self, report, contract_version: int) -> ContractFunction: """ Submits report data for processing. data. See the `ReportData` structure's docs for details. diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py index fe94e4a91..580c2aaf7 100644 --- a/src/providers/execution/contracts/hash_consensus.py +++ b/src/providers/execution/contracts/hash_consensus.py @@ -1,14 +1,14 @@ import logging from functools import lru_cache -from eth_typing import ChecksumAddress, Hash32 -from web3.types import TxParams, BlockIdentifier +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import BlockIdentifier from src.modules.submodules.types import ChainConfig, CurrentFrame, FrameConfig from src.providers.execution.base_interface import ContractInterface from src.utils.abi import named_tuple_to_dataclass - logger = logging.getLogger(__name__) @@ -109,7 +109,7 @@ def get_consensus_state_for_member(self, address: ChecksumAddress, block_identif return response - def submit_report(self, ref_slot: int, report_hash: Hash32, consensus_version: int) -> TxParams: + def submit_report(self, ref_slot: int, report_hash: bytes, consensus_version: int) -> ContractFunction: """ Used by oracle members to submit hash of the data calculated for the given reference slot. @@ -127,7 +127,7 @@ def submit_report(self, ref_slot: int, report_hash: Hash32, consensus_version: i logger.info({ 'msg': 'Build `submitReport({}, {}, {})`.'.format( # pylint: disable=consider-using-f-string ref_slot, - report_hash, + str(report_hash), consensus_version, ), }) diff --git a/src/services/validator_state.py b/src/services/validator_state.py index 31a0d024b..c6df7b20e 100644 --- a/src/services/validator_state.py +++ b/src/services/validator_state.py @@ -124,7 +124,7 @@ def get_operators_with_last_exited_validator_indexes(self, blockstamp: BlockStam last_requested_ids = self.w3.lido_contracts.validators_exit_bus_oracle.get_last_requested_validator_indices( module.id, - [no.id for no in node_operators], + tuple(no.id for no in node_operators), blockstamp.block_hash, ) diff --git a/tests/modules/accounting/test_accounting_module.py b/tests/modules/accounting/test_accounting_module.py index 1f24b08bb..916235b09 100644 --- a/tests/modules/accounting/test_accounting_module.py +++ b/tests/modules/accounting/test_accounting_module.py @@ -447,7 +447,7 @@ def test_simulate_rebase_after_report( @pytest.mark.unit @pytest.mark.usefixtures('lido_validators') def test_get_newly_exited_validators_by_modules(accounting: Accounting, ref_bs: ReferenceBlockStamp): - accounting.w3.lido_validators.get_staking_modules = Mock(return_value=[Mock(), Mock()]) + accounting.w3.lido_contracts.staking_router.get_staking_modules = Mock(return_value=[Mock(), Mock()]) accounting.lido_validator_state_service.get_exited_lido_validators = Mock(return_value=[]) RESULT = object() @@ -456,7 +456,7 @@ def test_get_newly_exited_validators_by_modules(accounting: Accounting, ref_bs: out = accounting._get_newly_exited_validators_by_modules(ref_bs) assert out is RESULT - accounting.w3.lido_validators.get_staking_modules.assert_called_once_with(ref_bs) + accounting.w3.lido_contracts.staking_router.get_staking_modules.assert_called_once_with(ref_bs.block_hash) accounting.lido_validator_state_service.get_exited_lido_validators.assert_called_once_with(ref_bs) From 4ca97cb81b34485598c5f1bbdafafc6c67c4f1bc Mon Sep 17 00:00:00 2001 From: F4ever Date: Thu, 18 Apr 2024 17:50:52 +0200 Subject: [PATCH 12/21] fix linter --- src/modules/submodules/consensus.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index d78add534..c0a361492 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -4,7 +4,6 @@ from typing import Optional, cast from eth_abi import encode -from eth_typing import Hash32 from hexbytes import HexBytes from src import variables From e850e406b5741c8be308687d8ac546acadbfcbdd Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 02:58:47 +0200 Subject: [PATCH 13/21] fix comments --- src/modules/submodules/consensus.py | 8 ++++---- src/modules/submodules/types.py | 3 --- src/providers/execution/base_interface.py | 4 ++-- src/providers/execution/contracts/accounting_oracle.py | 2 +- src/providers/execution/contracts/base_oracle.py | 2 +- src/providers/execution/contracts/exit_bus_oracle.py | 2 +- src/providers/execution/contracts/hash_consensus.py | 2 +- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index c0a361492..2b1fda766 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -251,7 +251,7 @@ def process_report(self, blockstamp: ReferenceBlockStamp) -> None: # Even if report hash transaction was failed we have to check if we can report data for current frame self._process_report_data(blockstamp, report_data, report_hash) - def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: bytes) -> None: + def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: HexBytes) -> None: latest_blockstamp, member_info = self._get_latest_data() if not member_info.is_report_member: @@ -288,8 +288,8 @@ def _process_report_data(self, blockstamp: ReferenceBlockStamp, report_data: tup msg = 'Oracle`s hash differs from consensus report hash.' logger.error({ 'msg': msg, - 'consensus_report_hash': str(HexBytes(member_info.current_frame_consensus_report)), - 'report_hash': str(report_hash), + 'consensus_report_hash': HexBytes(member_info.current_frame_consensus_report).hex(), + 'report_hash': report_hash.hex(), }) return @@ -341,7 +341,7 @@ def _get_latest_data(self) -> tuple[BlockStamp, MemberInfo]: return latest_blockstamp, member_info - def _encode_data_hash(self, report_data: tuple) -> bytes: + def _encode_data_hash(self, report_data: tuple) -> HexBytes: # The Accounting Oracle and Ejector Bus has same named method to report data report_function_name = 'submitReportData' diff --git a/src/modules/submodules/types.py b/src/modules/submodules/types.py index 9ae3195fd..5cd940b86 100644 --- a/src/modules/submodules/types.py +++ b/src/modules/submodules/types.py @@ -39,6 +39,3 @@ class FrameConfig: initial_epoch: int epochs_per_frame: int fast_lane_length_slots: int - - -ZERO_HASH = bytes([0]*32) diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index 2f02b134a..f1f7ac224 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -1,5 +1,5 @@ import json -from typing import Optional, Any, Type +from typing import Optional, Any, Type, Self from web3 import Web3 @@ -15,7 +15,7 @@ def load_abi(abi_file: str) -> dict: return json.load(abi_json) @classmethod - def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> Type['ContractInterface']: + def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> Self: if cls.abi_path is None: raise AttributeError(f'abi_path attribute is missing in {cls.__name__} class') diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py index 7331d10ad..84af7cc6f 100644 --- a/src/providers/execution/contracts/accounting_oracle.py +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -43,5 +43,5 @@ def submit_report_extra_data_list(self, extra_data: bytes) -> ContractFunction: Submits report extra data in the EXTRA_DATA_FORMAT_LIST format for processing. """ tx = self.functions.submitReportExtraDataList(extra_data) - logger.info({'msg': f'Build `submitReportExtraDataList({str(extra_data)})` tx.'}) + logger.info({'msg': f'Build `submitReportExtraDataList({extra_data.hex()})` tx.'}) return tx diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py index 97136288b..7a9ae2279 100644 --- a/src/providers/execution/contracts/base_oracle.py +++ b/src/providers/execution/contracts/base_oracle.py @@ -47,7 +47,7 @@ def has_role(self, role: Hash32, address: ChecksumAddress, block_identifier: Blo """ response = self.functions.hasRole(role, address).call(block_identifier=block_identifier) logger.info({ - 'msg': f'Call `hasRole({str(role)}, {address})`.', + 'msg': f'Call `hasRole({role.hex()}, {address})`.', 'value': response, 'block_identifier': repr(block_identifier), }) diff --git a/src/providers/execution/contracts/exit_bus_oracle.py b/src/providers/execution/contracts/exit_bus_oracle.py index 1c4f0cc93..772868226 100644 --- a/src/providers/execution/contracts/exit_bus_oracle.py +++ b/src/providers/execution/contracts/exit_bus_oracle.py @@ -60,7 +60,7 @@ def get_last_requested_validator_indices( ).call(block_identifier=block_identifier) logger.info({ - 'msg': 'Call `getLastRequestedValidatorIndices({module_id}, {node_operators_ids_in_module})`.', + 'msg': f'Call `getLastRequestedValidatorIndices({module_id}, {node_operators_ids_in_module})`.', 'value': response, 'block_identifier': repr(block_identifier), }) diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py index 580c2aaf7..57bacb56b 100644 --- a/src/providers/execution/contracts/hash_consensus.py +++ b/src/providers/execution/contracts/hash_consensus.py @@ -127,7 +127,7 @@ def submit_report(self, ref_slot: int, report_hash: bytes, consensus_version: in logger.info({ 'msg': 'Build `submitReport({}, {}, {})`.'.format( # pylint: disable=consider-using-f-string ref_slot, - str(report_hash), + report_hash.hex(), consensus_version, ), }) From f447f93ac967d767f1562c6c9c1c7c4d754a25d6 Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 03:23:01 +0200 Subject: [PATCH 14/21] pylint fixed --- src/providers/execution/base_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index f1f7ac224..d166439b3 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -1,5 +1,5 @@ import json -from typing import Optional, Any, Type, Self +from typing import Optional, Any, Self from web3 import Web3 From e4947cdb934084ea175ab7c17fdf096e832a2c67 Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 03:41:17 +0200 Subject: [PATCH 15/21] fix lru cache --- .../execution/contracts/accounting_oracle.py | 2 +- src/providers/execution/contracts/base_oracle.py | 2 +- src/providers/execution/contracts/burner.py | 2 +- .../execution/contracts/exit_bus_oracle.py | 2 +- src/providers/execution/contracts/hash_consensus.py | 2 +- src/providers/execution/contracts/lido_locator.py | 2 +- .../execution/contracts/oracle_daemon_config.py | 2 +- .../contracts/oracle_report_sanity_checker.py | 2 +- src/providers/execution/contracts/staking_router.py | 2 +- .../execution/contracts/withdrawal_queue_nft.py | 2 +- src/utils/cache.py | 13 +++++++++++++ 11 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/providers/execution/contracts/accounting_oracle.py b/src/providers/execution/contracts/accounting_oracle.py index 84af7cc6f..c1a6f2471 100644 --- a/src/providers/execution/contracts/accounting_oracle.py +++ b/src/providers/execution/contracts/accounting_oracle.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3.contract.contract import ContractFunction from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/base_oracle.py b/src/providers/execution/contracts/base_oracle.py index 7a9ae2279..578c08b88 100644 --- a/src/providers/execution/contracts/base_oracle.py +++ b/src/providers/execution/contracts/base_oracle.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from eth_typing import ChecksumAddress, Hash32 from web3.contract.contract import ContractFunction diff --git a/src/providers/execution/contracts/burner.py b/src/providers/execution/contracts/burner.py index 6a14d98a9..89641555e 100644 --- a/src/providers/execution/contracts/burner.py +++ b/src/providers/execution/contracts/burner.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/exit_bus_oracle.py b/src/providers/execution/contracts/exit_bus_oracle.py index 772868226..1bb8485fe 100644 --- a/src/providers/execution/contracts/exit_bus_oracle.py +++ b/src/providers/execution/contracts/exit_bus_oracle.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from typing import Sequence from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/hash_consensus.py b/src/providers/execution/contracts/hash_consensus.py index 57bacb56b..b59d7e755 100644 --- a/src/providers/execution/contracts/hash_consensus.py +++ b/src/providers/execution/contracts/hash_consensus.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction diff --git a/src/providers/execution/contracts/lido_locator.py b/src/providers/execution/contracts/lido_locator.py index 99cd2d112..986e000fd 100644 --- a/src/providers/execution/contracts/lido_locator.py +++ b/src/providers/execution/contracts/lido_locator.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from eth_typing import ChecksumAddress from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/oracle_daemon_config.py b/src/providers/execution/contracts/oracle_daemon_config.py index dbce1cf4d..bb66091d7 100644 --- a/src/providers/execution/contracts/oracle_daemon_config.py +++ b/src/providers/execution/contracts/oracle_daemon_config.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3 import Web3 from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/oracle_report_sanity_checker.py b/src/providers/execution/contracts/oracle_report_sanity_checker.py index 9541a5221..ef40f1de2 100644 --- a/src/providers/execution/contracts/oracle_report_sanity_checker.py +++ b/src/providers/execution/contracts/oracle_report_sanity_checker.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/staking_router.py b/src/providers/execution/contracts/staking_router.py index 1719b5366..7d6fc2e7e 100644 --- a/src/providers/execution/contracts/staking_router.py +++ b/src/providers/execution/contracts/staking_router.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3.types import BlockIdentifier diff --git a/src/providers/execution/contracts/withdrawal_queue_nft.py b/src/providers/execution/contracts/withdrawal_queue_nft.py index 28b0c87e9..c8b9f3289 100644 --- a/src/providers/execution/contracts/withdrawal_queue_nft.py +++ b/src/providers/execution/contracts/withdrawal_queue_nft.py @@ -1,5 +1,5 @@ import logging -from functools import lru_cache +from src.utils.cache import global_lru_cache as lru_cache from web3.types import Wei, BlockIdentifier diff --git a/src/utils/cache.py b/src/utils/cache.py index 7e8d7391b..97666d248 100644 --- a/src/utils/cache.py +++ b/src/utils/cache.py @@ -1,6 +1,10 @@ import functools from weakref import WeakKeyDictionary +from web3.types import BlockParams + +from src.providers.execution.base_interface import ContractInterface + global_cache: WeakKeyDictionary = WeakKeyDictionary() @@ -9,6 +13,15 @@ def caching_decorator(func): cached_func = functools.lru_cache(*args, **kwargs)(func) def wrapper(*args, **kwargs): + + # If lru_cache is on contract + # Do not cache any requests with relative blocks + # Like 'latest', 'earliest', 'pending', 'safe', 'finalized' or if default block provided + if issubclass(args[0].__class__, ContractInterface): + block = kwargs.get('block_identifier', None) + if block is None or block in BlockParams.__args__: + return func(*args, **kwargs) + result = cached_func(*args, **kwargs) global_cache[func] = cached_func return result From 0649b8c5f714f46933f5cc2735812e8617363878 Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 03:46:35 +0200 Subject: [PATCH 16/21] test for cache --- tests/utils/test_cache.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index e97e7461f..db72d5af3 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,3 +1,6 @@ +from web3.types import BlockIdentifier + +from src.providers.execution.base_interface import ContractInterface from src.utils.cache import clear_global_cache, global_lru_cache @@ -15,3 +18,25 @@ def test_clear_global_cache(): clear_global_cache() assert calc.get.cache_info().currsize == 0 + + +class Contract(ContractInterface): + def __init__(self): + pass + + @global_lru_cache(maxsize=5) + def func(self, block_identifier: BlockIdentifier = 'latest'): + pass + + +def test_cache_do_not_cache_contract_with_relative_blocks(): + c = Contract() + + c.func() + assert c.func.cache_info().currsize == 0 + c.func(block_identifier='1') + c.func(block_identifier='1') + c.func(block_identifier='2') + c.func(block_identifier='latest') + c.func(block_identifier='finalized') + assert c.func.cache_info().currsize == 2 From 7fb3b452787fe7a3186e6469bb1d33e9fc327a1c Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 03:49:12 +0200 Subject: [PATCH 17/21] fix linter --- src/utils/dataclass.py | 4 ++-- tests/utils/test_cache.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/dataclass.py b/src/utils/dataclass.py index 6f880e148..c23f25904 100644 --- a/src/utils/dataclass.py +++ b/src/utils/dataclass.py @@ -1,7 +1,7 @@ import functools from dataclasses import dataclass, fields, is_dataclass from types import GenericAlias -from typing import Callable, Self, Sequence, TypeVar +from typing import Callable, Self, Sequence, TypeVar, Type from src.utils.abi import named_tuple_to_dataclass @@ -47,7 +47,7 @@ class FromResponse: """ @classmethod - def from_response(cls, **kwargs) -> Self: + def from_response(cls, **kwargs) -> Type[Self]: class_field_names = [field.name for field in fields(cls)] return cls(**{k: v for k, v in kwargs.items() if k in class_field_names}) diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index db72d5af3..1eae2f377 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,3 +1,4 @@ +from hexbytes import HexBytes from web3.types import BlockIdentifier from src.providers.execution.base_interface import ContractInterface @@ -34,9 +35,9 @@ def test_cache_do_not_cache_contract_with_relative_blocks(): c.func() assert c.func.cache_info().currsize == 0 - c.func(block_identifier='1') - c.func(block_identifier='1') - c.func(block_identifier='2') + c.func(block_identifier=HexBytes('11')) + c.func(block_identifier=HexBytes('11')) + c.func(block_identifier=HexBytes('22')) c.func(block_identifier='latest') c.func(block_identifier='finalized') assert c.func.cache_info().currsize == 2 From 046159ddca24f72134449bb97cc9263f267d93c6 Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 04:05:44 +0200 Subject: [PATCH 18/21] fix linters --- src/providers/execution/base_interface.py | 4 ++-- src/utils/dataclass.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/providers/execution/base_interface.py b/src/providers/execution/base_interface.py index d166439b3..c40b19583 100644 --- a/src/providers/execution/base_interface.py +++ b/src/providers/execution/base_interface.py @@ -1,5 +1,5 @@ import json -from typing import Optional, Any, Self +from typing import Optional, Any, Self, Type from web3 import Web3 @@ -15,7 +15,7 @@ def load_abi(abi_file: str) -> dict: return json.load(abi_json) @classmethod - def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> Self: + def factory(cls, w3: Web3, class_name: Optional[str] = None, **kwargs: Any) -> Type[Self]: if cls.abi_path is None: raise AttributeError(f'abi_path attribute is missing in {cls.__name__} class') diff --git a/src/utils/dataclass.py b/src/utils/dataclass.py index c23f25904..d162cc4c6 100644 --- a/src/utils/dataclass.py +++ b/src/utils/dataclass.py @@ -47,7 +47,7 @@ class FromResponse: """ @classmethod - def from_response(cls, **kwargs) -> Type[Self]: + def from_response(cls, **kwargs) -> Self: class_field_names = [field.name for field in fields(cls)] return cls(**{k: v for k, v in kwargs.items() if k in class_field_names}) From 9548130d850450db2150171a8ffd49495979824e Mon Sep 17 00:00:00 2001 From: F4ever Date: Sat, 20 Apr 2024 17:10:48 +0200 Subject: [PATCH 19/21] remove unused import --- src/utils/dataclass.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/dataclass.py b/src/utils/dataclass.py index d162cc4c6..6f880e148 100644 --- a/src/utils/dataclass.py +++ b/src/utils/dataclass.py @@ -1,7 +1,7 @@ import functools from dataclasses import dataclass, fields, is_dataclass from types import GenericAlias -from typing import Callable, Self, Sequence, TypeVar, Type +from typing import Callable, Self, Sequence, TypeVar from src.utils.abi import named_tuple_to_dataclass From c54b03b3ac9676ef488c1a75681e26a8c6327cff Mon Sep 17 00:00:00 2001 From: F4ever Date: Wed, 24 Apr 2024 01:21:36 +0200 Subject: [PATCH 20/21] unify get call function --- .../contracts/oracle_daemon_config.py | 104 +++++------------- 1 file changed, 27 insertions(+), 77 deletions(-) diff --git a/src/providers/execution/contracts/oracle_daemon_config.py b/src/providers/execution/contracts/oracle_daemon_config.py index bb66091d7..61072193a 100644 --- a/src/providers/execution/contracts/oracle_daemon_config.py +++ b/src/providers/execution/contracts/oracle_daemon_config.py @@ -1,4 +1,6 @@ import logging +from typing import Any + from src.utils.cache import global_lru_cache as lru_cache from web3 import Web3 @@ -13,109 +15,57 @@ class OracleDaemonConfigContract(ContractInterface): abi_path = './assets/OracleDaemonConfig.json' - @lru_cache(maxsize=1) - def normalized_cl_reward_per_epoch(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('NORMALIZED_CL_REWARD_PER_EPOCH').call(block_identifier=block_identifier) + def _get(self, param: str, block_identifier: BlockIdentifier = 'latest') -> Any: + response = self.functions.get(param).call(block_identifier=block_identifier) - response = Web3.to_int(response) logger.info({ - 'msg': 'Call `NORMALIZED_CL_REWARD_PER_EPOCH()`.', + 'msg': f'Call `get({param})`.', 'value': response, 'block_identifier': repr(block_identifier), }) return response @lru_cache(maxsize=1) - def normalized_cl_reward_mistake_rate_bp(self, block_identifier: BlockIdentifier = 'latest') -> float: - response = self.functions.get('NORMALIZED_CL_REWARD_MISTAKE_RATE_BP').call(block_identifier=block_identifier) + def normalized_cl_reward_per_epoch(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self._get('NORMALIZED_CL_REWARD_PER_EPOCH', block_identifier) + return Web3.to_int(response) - response = Web3.to_int(response) - logger.info({ - 'msg': 'Call `NORMALIZED_CL_REWARD_MISTAKE_RATE_BP()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + @lru_cache(maxsize=1) + def normalized_cl_reward_mistake_rate_bp(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self._get('NORMALIZED_CL_REWARD_MISTAKE_RATE_BP', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def rebase_check_nearest_epoch_distance(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('REBASE_CHECK_NEAREST_EPOCH_DISTANCE').call(block_identifier=block_identifier) - - response = Web3.to_int(response) - logger.info({ - 'msg': 'Call `REBASE_CHECK_NEAREST_EPOCH_DISTANCE()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('REBASE_CHECK_NEAREST_EPOCH_DISTANCE', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def rebase_check_distant_epoch_distance(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('REBASE_CHECK_DISTANT_EPOCH_DISTANCE').call(block_identifier=block_identifier) - - response = Web3.to_int(response) - logger.info({ - 'msg': 'Call `REBASE_CHECK_DISTANT_EPOCH_DISTANCE()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('REBASE_CHECK_DISTANT_EPOCH_DISTANCE', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) - def node_operator_network_penetration_threshold_bp(self, block_identifier: BlockIdentifier = 'latest') -> float: - response = self.functions.get('NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP').call(block_identifier=block_identifier) - - response = Web3.to_int(response) - logger.info({ - 'msg': 'Call `NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + def node_operator_network_penetration_threshold_bp(self, block_identifier: BlockIdentifier = 'latest') -> int: + response = self._get('NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def prediction_duration_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('PREDICTION_DURATION_IN_SLOTS').call(block_identifier=block_identifier) - - response = Web3.to_int(response) - logger.info({ - 'msg': 'Call `PREDICTION_DURATION_IN_SLOTS()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('PREDICTION_DURATION_IN_SLOTS', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def finalization_max_negative_rebase_epoch_shift(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT').call(block_identifier=block_identifier) - - response = Web3.to_int(primitive=response) - - logger.info({ - 'msg': 'Call `FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def validator_delayed_timeout_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS').call(block_identifier=block_identifier) - - logger.info({ - 'msg': 'Call `VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS', block_identifier) + return Web3.to_int(response) @lru_cache(maxsize=1) def validator_delinquent_timeout_in_slots(self, block_identifier: BlockIdentifier = 'latest') -> int: - response = self.functions.get('VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS').call(block_identifier=block_identifier) - - logger.info({ - 'msg': 'Call `VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS()`.', - 'value': response, - 'block_identifier': repr(block_identifier), - }) - return response + response = self._get('VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS', block_identifier) + return Web3.to_int(response) From 54c4c703196bc49a9aebc3059d5bf4437939b89d Mon Sep 17 00:00:00 2001 From: F4ever Date: Wed, 24 Apr 2024 01:38:42 +0200 Subject: [PATCH 21/21] back to HexBytes --- src/modules/submodules/consensus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/submodules/consensus.py b/src/modules/submodules/consensus.py index 2b1fda766..63aa0ebe9 100644 --- a/src/modules/submodules/consensus.py +++ b/src/modules/submodules/consensus.py @@ -277,7 +277,7 @@ def _process_report_hash(self, blockstamp: ReferenceBlockStamp, report_hash: Hex self._send_report_hash(blockstamp, report_hash, self.CONSENSUS_VERSION) return None - def _process_report_data(self, blockstamp: ReferenceBlockStamp, report_data: tuple, report_hash: bytes): + def _process_report_data(self, blockstamp: ReferenceBlockStamp, report_data: tuple, report_hash: HexBytes): latest_blockstamp, member_info = self._get_latest_data() if member_info.current_frame_consensus_report == ZERO_HASH: