#![allow(clippy::enum_variant_names)] #![allow(missing_docs)] use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use ethers::providers::Middleware; use ethers_contract::builders::ContractCall; use hyperlane_core::{ Announcement, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256, }; use tracing::{instrument, log::trace}; use crate::{ contracts::i_validator_announce::{ IValidatorAnnounce as EthereumValidatorAnnounceInternal, IVALIDATORANNOUNCE_ABI, }, trait_builder::BuildableWithProvider, tx::{fill_tx_gas_params, report_tx}, EthereumProvider, }; impl std::fmt::Display for EthereumValidatorAnnounceInternal where M: Middleware, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } pub struct ValidatorAnnounceBuilder {} #[async_trait] impl BuildableWithProvider for ValidatorAnnounceBuilder { type Output = Box; async fn build_with_provider( &self, provider: M, locator: &ContractLocator, ) -> Self::Output { Box::new(EthereumValidatorAnnounce::new(Arc::new(provider), locator)) } } /// A reference to a ValidatorAnnounce contract on some Ethereum chain #[derive(Debug)] pub struct EthereumValidatorAnnounce where M: Middleware, { contract: Arc>, domain: HyperlaneDomain, provider: Arc, } impl EthereumValidatorAnnounce where M: Middleware + 'static, { /// Create a reference to a ValidatoAnnounce contract at a specific Ethereum /// address on some chain pub fn new(provider: Arc, locator: &ContractLocator) -> Self { Self { contract: Arc::new(EthereumValidatorAnnounceInternal::new( locator.address, provider.clone(), )), domain: locator.domain.clone(), provider, } } /// Returns a ContractCall that processes the provided message. /// If the provided tx_gas_limit is None, gas estimation occurs. async fn announce_contract_call( &self, announcement: SignedType, tx_gas_limit: Option, ) -> ChainResult> { let serialized_signature: [u8; 65] = announcement.signature.into(); let tx = self.contract.announce( announcement.value.validator.into(), announcement.value.storage_location, serialized_signature.into(), ); fill_tx_gas_params(tx, tx_gas_limit, self.provider.clone()).await } } impl HyperlaneChain for EthereumValidatorAnnounce where M: Middleware + 'static, { fn domain(&self) -> &HyperlaneDomain { &self.domain } fn provider(&self) -> Box { Box::new(EthereumProvider::new( self.contract.client(), self.domain.clone(), )) } } impl HyperlaneContract for EthereumValidatorAnnounce where M: Middleware + 'static, { fn address(&self) -> H256 { self.contract.address().into() } } #[async_trait] impl ValidatorAnnounce for EthereumValidatorAnnounce where M: Middleware + 'static, { async fn get_announced_storage_locations( &self, validators: &[H256], ) -> ChainResult>> { let storage_locations = self .contract .get_announced_storage_locations( validators.iter().map(|v| H160::from(*v).into()).collect(), ) .call() .await?; Ok(storage_locations) } #[instrument(ret, skip(self))] async fn announce_tokens_needed(&self, announcement: SignedType) -> Option { let validator = announcement.value.validator; let eth_h160: ethers::types::H160 = validator.into(); let Ok(contract_call) = self.announce_contract_call(announcement, None).await else { trace!("Unable to get announce contract call"); return None; }; let Ok(balance) = self.provider.get_balance(eth_h160, None).await else { trace!("Unable to query balance"); return None; }; let Some(max_cost) = contract_call.tx.max_cost() else { trace!("Unable to get announce max cost"); return None; }; Some(max_cost.saturating_sub(balance).into()) } #[instrument(err, ret, skip(self))] async fn announce( &self, announcement: SignedType, tx_gas_limit: Option, ) -> ChainResult { let contract_call = self .announce_contract_call(announcement, tx_gas_limit) .await?; let receipt = report_tx(contract_call).await?; Ok(receipt.into()) } } pub struct EthereumValidatorAnnounceAbi; impl HyperlaneAbi for EthereumValidatorAnnounceAbi { const SELECTOR_SIZE_BYTES: usize = 4; fn fn_map() -> HashMap, &'static str> { super::extract_fn_map(&IVALIDATORANNOUNCE_ABI) } }