Skip to content

Commit

Permalink
Merge pull request #432 from lidofinance/feat/abstract-contract-inter…
Browse files Browse the repository at this point in the history
…action

Feat: Abstract contract interaction
  • Loading branch information
F4ever authored May 6, 2024
2 parents 0a014df + 54c4c70 commit 2af069b
Show file tree
Hide file tree
Showing 84 changed files with 1,631 additions and 885 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ branch = true

[tool.pylint.format]
max-line-length = "120"
min-similarity-lines=6


[tool.pylint."messages control"]
Expand Down
4 changes: 2 additions & 2 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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

Expand Down
60 changes: 15 additions & 45 deletions src/modules/accounting/accounting.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

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,
AccountingProcessingState,
LidoReportRebase,
SharesRequestedToBurn,
)
from src.metrics.prometheus.accounting import (
ACCOUNTING_IS_BUNKER,
Expand All @@ -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.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.extensions.lido_validators import StakingModule, NodeOperatorGlobalIndex, StakingModuleId
from src.web3py.types import Web3
from src.web3py.extensions.lido_validators import StakingModule


logger = logging.getLogger(__name__)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -183,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)
Expand Down Expand Up @@ -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
Expand All @@ -273,31 +262,12 @@ 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,
blockstamp.block_hash,
)

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):
Expand Down
6 changes: 3 additions & 3 deletions src/modules/accounting/extra_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

from hexbytes import HexBytes

from src.modules.submodules.typings import ZERO_HASH
from src.web3py.extensions.lido_validators 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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.types import SlotNumber, Gwei, StakingModuleId


@dataclass
Expand Down Expand Up @@ -81,29 +80,33 @@ class LidoReportRebase:
el_reward: Wei


@dataclass
class Account:
address: ChecksumAddress
_private_key: HexBytes


@dataclass
class BatchState:
remaining_eth_budget: int
finished: bool
batches: list[int]
batches: tuple[int, ...]
batches_length: int

def as_tuple(self):
return (
self.remaining_eth_budget,
self.finished,
self.batches,
self.batches_length
self.batches_length,
)


@dataclass
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
4 changes: 2 additions & 2 deletions src/modules/checks/suites/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,7 +16,7 @@
LidoContracts,
FallbackProviderModule,
)
from src.web3py.typings import Web3
from src.web3py.types import Web3


TITLE_PROPERTY_NAME = "test_title"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/checks/suites/consensus_node.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
56 changes: 13 additions & 43 deletions src/modules/ejector/ejector.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@
)
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 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
from src.utils.abi import named_tuple_to_dataclass
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,
is_fully_withdrawable_validator,
is_partially_withdrawable_validator,
)
from src.web3py.extensions.lido_validators import LidoValidator, NodeOperatorGlobalIndex
from src.web3py.typings import Web3
from src.web3py.extensions.lido_validators import LidoValidator
from src.web3py.types import Web3

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass

from src.typings import SlotNumber
from src.types import SlotNumber


@dataclass
Expand Down
Loading

0 comments on commit 2af069b

Please sign in to comment.