diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index b6f02ecfe..6dfcb67ce 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -4629,6 +4629,7 @@ dependencies = [ "hyperlane-sealevel-multisig-ism-message-id", "hyperlane-sealevel-validator-announce", "jsonrpc-core", + "lazy_static", "multisig-ism", "num-traits", "reqwest", diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 0f1ec5f74..e9192b7f0 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -83,6 +83,7 @@ itertools = "*" jobserver = "=0.1.26" jsonrpc-core = "18.0" k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] } +lazy_static = "1.5.0" log = "0.4" macro_rules_attribute = "0.2" maplit = "1.0" diff --git a/rust/main/chains/hyperlane-sealevel/Cargo.toml b/rust/main/chains/hyperlane-sealevel/Cargo.toml index 3a1bda12f..666ebee87 100644 --- a/rust/main/chains/hyperlane-sealevel/Cargo.toml +++ b/rust/main/chains/hyperlane-sealevel/Cargo.toml @@ -11,6 +11,7 @@ bincode.workspace = true borsh.workspace = true derive-new.workspace = true jsonrpc-core.workspace = true +lazy_static.workspace = true num-traits.workspace = true reqwest.workspace = true serde.workspace = true diff --git a/rust/main/chains/hyperlane-sealevel/src/error.rs b/rust/main/chains/hyperlane-sealevel/src/error.rs index 569c5cff5..cb9306158 100644 --- a/rust/main/chains/hyperlane-sealevel/src/error.rs +++ b/rust/main/chains/hyperlane-sealevel/src/error.rs @@ -42,6 +42,12 @@ pub enum HyperlaneSealevelError { /// Empty compute units consumed #[error("received empty compute units consumed in transaction")] EmptyComputeUnitsConsumed, + /// Too many non-native programs + #[error("transaction contains too many non-native programs, hash: {0:?}")] + TooManyNonNativePrograms(H512), + /// No non-native programs + #[error("transaction contains no non-native programs, hash: {0:?}")] + NoNonNativePrograms(H512), } impl From for ChainCommunicationError { diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 144cc9c96..7c431558a 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -1,10 +1,13 @@ +use std::collections::HashSet; use std::sync::Arc; use async_trait::async_trait; +use lazy_static::lazy_static; use solana_sdk::signature::Signature; use solana_transaction_status::{ option_serializer::OptionSerializer, EncodedTransaction, EncodedTransactionWithStatusMeta, - UiMessage, UiTransaction, UiTransactionStatusMeta, + UiInstruction, UiMessage, UiParsedInstruction, UiParsedMessage, UiTransaction, + UiTransactionStatusMeta, }; use tracing::warn; @@ -18,6 +21,19 @@ use crate::error::HyperlaneSealevelError; use crate::utils::{decode_h256, decode_h512, decode_pubkey}; use crate::{ConnectionConf, SealevelRpcClient}; +lazy_static! { + static ref NATIVE_PROGRAMS: HashSet = HashSet::from([ + solana_sdk::bpf_loader_upgradeable::ID.to_string(), + solana_sdk::compute_budget::ID.to_string(), + solana_sdk::config::program::ID.to_string(), + solana_sdk::ed25519_program::ID.to_string(), + solana_sdk::secp256k1_program::ID.to_string(), + solana_sdk::stake::program::ID.to_string(), + solana_sdk::system_program::ID.to_string(), + solana_sdk::vote::program::ID.to_string(), + ]); +} + /// A wrapper around a Sealevel provider to get generic blockchain information. #[derive(Debug)] pub struct SealevelProvider { @@ -33,7 +49,7 @@ impl SealevelProvider { let rpc_client = Arc::new(SealevelRpcClient::new(conf.url.to_string())); let native_token = conf.native_token.clone(); - SealevelProvider { + Self { domain, rpc_client, native_token, @@ -64,12 +80,7 @@ impl SealevelProvider { } fn sender(hash: &H512, txn: &UiTransaction) -> ChainResult { - let message = match &txn.message { - UiMessage::Parsed(m) => m, - m => Err(Into::::into( - HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()), - ))?, - }; + let message = Self::parsed_message(txn)?; let signer = message .account_keys @@ -80,6 +91,39 @@ impl SealevelProvider { Ok(sender) } + fn recipient(hash: &H512, txn: &UiTransaction) -> ChainResult { + let message = Self::parsed_message(txn)?; + + let programs = message + .instructions + .iter() + .filter_map(|ii| { + if let UiInstruction::Parsed(iii) = ii { + Some(iii) + } else { + None + } + }) + .map(|ii| match ii { + UiParsedInstruction::Parsed(iii) => &iii.program_id, + UiParsedInstruction::PartiallyDecoded(iii) => &iii.program_id, + }) + .filter(|program_id| !NATIVE_PROGRAMS.contains(*program_id)) + .collect::>(); + + if programs.len() > 1 { + Err(HyperlaneSealevelError::TooManyNonNativePrograms(*hash))?; + } + + let program_id = programs + .first() + .ok_or(HyperlaneSealevelError::NoNonNativePrograms(*hash))?; + + let pubkey = decode_pubkey(program_id)?; + let recipient = H256::from_slice(&pubkey.to_bytes()); + Ok(recipient) + } + fn gas(meta: &UiTransactionStatusMeta) -> ChainResult { let OptionSerializer::Some(gas) = meta.compute_units_consumed else { Err(HyperlaneSealevelError::EmptyComputeUnitsConsumed)? @@ -108,6 +152,15 @@ impl SealevelProvider { .ok_or(HyperlaneSealevelError::EmptyMetadata)?; Ok(meta) } + + fn parsed_message(txn: &UiTransaction) -> ChainResult<&UiParsedMessage> { + Ok(match &txn.message { + UiMessage::Parsed(m) => m, + m => Err(Into::::into( + HyperlaneSealevelError::UnsupportedMessageEncoding(m.clone()), + ))?, + }) + } } impl HyperlaneChain for SealevelProvider { @@ -165,6 +218,7 @@ impl HyperlaneProvider for SealevelProvider { Self::validate_transaction(hash, txn)?; let sender = Self::sender(hash, txn)?; + let recipient = Self::recipient(hash, txn)?; let meta = Self::meta(txn_with_meta)?; let gas_used = Self::gas(meta)?; let fee = self.fee(meta)?; @@ -189,7 +243,7 @@ impl HyperlaneProvider for SealevelProvider { gas_price, nonce: 0, sender, - recipient: None, + recipient: Some(recipient), receipt: Some(receipt), raw_input_data: None, })