diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 19f764e785..2d3e25700d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4155,6 +4155,7 @@ dependencies = [ "base64 0.21.4", "bech32 0.9.1", "cosmrs", + "derive-new", "hex 0.4.3", "hpl-interface 0.0.2", "hyper", @@ -4166,6 +4167,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha256", + "tendermint", "thiserror", "tokio", "tonic", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e7a6e3a9cb..3345fe9887 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -149,6 +149,7 @@ static_assertions = "1.1" strum = "0.25.0" strum_macros = "0.25.2" tempfile = "3.3" +tendermint = "0.32.2" thiserror = "1.0" time = "0.3" tiny-keccak = "2.0.2" diff --git a/rust/chains/hyperlane-cosmos/Cargo.toml b/rust/chains/hyperlane-cosmos/Cargo.toml index 1825ec7174..e3ff363ae9 100644 --- a/rust/chains/hyperlane-cosmos/Cargo.toml +++ b/rust/chains/hyperlane-cosmos/Cargo.toml @@ -10,6 +10,7 @@ version = { workspace = true } [dependencies] async-trait = { workspace = true } cosmrs = { workspace = true, features = ["cosmwasm", "tokio", "grpc", "rpc"] } +derive-new = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } @@ -27,12 +28,7 @@ hyper = { workspace = true } hyper-tls = { workspace = true } sha256 = { workspace = true } hex = { workspace = true } +tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"]} hpl-interface = { version = "0.0.2" } hyperlane-core = { path = "../../hyperlane-core" } - -# These should only be used if it _must_ be used to interop with the inner library, -# all errors exported from a chain crate should be using thiserror or handrolled to -# make error handling easier. -# eyre = "never" -# anyhow = never diff --git a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs index 6b60be5bcc..373d71a1fc 100644 --- a/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/aggregation_ism.rs @@ -1,7 +1,9 @@ +use std::str::FromStr; + use crate::{ + address::CosmosAddress, grpc::{WasmGrpcProvider, WasmProvider}, payloads::aggregate_ism::{ModulesAndThresholdRequest, ModulesAndThresholdResponse}, - verify::bech32_decode, ConnectionConf, CosmosProvider, Signer, }; use async_trait::async_trait; @@ -60,8 +62,11 @@ impl AggregationIsm for CosmosAggregationIsm { let data = self.provider.wasm_query(payload, None).await?; let response: ModulesAndThresholdResponse = serde_json::from_slice(&data)?; - let modules: ChainResult> = - response.modules.into_iter().map(bech32_decode).collect(); + let modules: ChainResult> = response + .modules + .into_iter() + .map(|module| CosmosAddress::from_str(&module).map(|ca| ca.digest())) + .collect(); Ok((modules?, response.threshold)) } diff --git a/rust/chains/hyperlane-cosmos/src/error.rs b/rust/chains/hyperlane-cosmos/src/error.rs index cb101f009e..7ed0d141ce 100644 --- a/rust/chains/hyperlane-cosmos/src/error.rs +++ b/rust/chains/hyperlane-cosmos/src/error.rs @@ -12,6 +12,12 @@ pub enum HyperlaneCosmosError { /// gRPC error #[error("{0}")] GrpcError(#[from] tonic::Status), + /// Cosmos error + #[error("{0}")] + CosmosError(#[from] cosmrs::Error), + /// Cosmos error report + #[error("{0}")] + CosmosErrorReport(#[from] cosmrs::ErrorReport), } impl From for ChainCommunicationError { diff --git a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs index 1e27fca038..5b122e2c02 100644 --- a/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/chains/hyperlane-cosmos/src/interchain_security_module.rs @@ -8,10 +8,9 @@ use crate::{ grpc::{WasmGrpcProvider, WasmProvider}, payloads::{ general::EmptyStruct, - ism_routes::{ - QueryIsmGeneralRequest, QueryIsmModuleTypeRequest, QueryIsmModuleTypeResponse, - }, + ism_routes::{QueryIsmGeneralRequest, QueryIsmModuleTypeRequest}, }, + types::IsmType, ConnectionConf, CosmosProvider, Signer, }; @@ -57,19 +56,6 @@ impl HyperlaneChain for CosmosInterchainSecurityModule { } } -fn ism_type_to_module_type(ism_type: hpl_interface::ism::ISMType) -> ModuleType { - match ism_type { - hpl_interface::ism::ISMType::Unused => ModuleType::Unused, - hpl_interface::ism::ISMType::Routing => ModuleType::Routing, - hpl_interface::ism::ISMType::Aggregation => ModuleType::Aggregation, - hpl_interface::ism::ISMType::LegacyMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::ISMType::MerkleRootMultisig => ModuleType::MerkleRootMultisig, - hpl_interface::ism::ISMType::MessageIdMultisig => ModuleType::MessageIdMultisig, - hpl_interface::ism::ISMType::Null => ModuleType::Null, - hpl_interface::ism::ISMType::CcipRead => ModuleType::CcipRead, - } -} - #[async_trait] impl InterchainSecurityModule for CosmosInterchainSecurityModule { /// Returns the module type of the ISM compliant with the corresponding @@ -84,16 +70,9 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { .wasm_query(QueryIsmGeneralRequest { ism: query }, None) .await?; - // Handle both the ISMType response and the ModuleTypeResponse response. - let ismtype_response = serde_json::from_slice::(&data); - let moduletye_response = - serde_json::from_slice::(&data); - - Ok(match (ismtype_response, moduletye_response) { - (Ok(v), _) => ism_type_to_module_type(v.typ), - (_, Ok(v)) => ism_type_to_module_type(v.typ), - _ => ModuleType::Null, - }) + let module_type_response = + serde_json::from_slice::(&data)?; + Ok(IsmType(module_type_response.typ).into()) } /// Dry runs the `verify()` ISM call and returns `Some(gas_estimate)` if the call diff --git a/rust/chains/hyperlane-cosmos/src/lib.rs b/rust/chains/hyperlane-cosmos/src/lib.rs index a68c97aee8..82a4a0ece1 100644 --- a/rust/chains/hyperlane-cosmos/src/lib.rs +++ b/rust/chains/hyperlane-cosmos/src/lib.rs @@ -18,6 +18,7 @@ mod providers; mod routing_ism; mod signers; mod trait_builder; +mod types; mod utils; mod validator_announce; diff --git a/rust/chains/hyperlane-cosmos/src/libs/address.rs b/rust/chains/hyperlane-cosmos/src/libs/address.rs new file mode 100644 index 0000000000..b9454eea92 --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/libs/address.rs @@ -0,0 +1,142 @@ +use std::str::FromStr; + +use cosmrs::{ + crypto::{secp256k1::SigningKey, PublicKey}, + AccountId, +}; +use derive_new::new; +use hyperlane_core::{ChainCommunicationError, ChainResult, Error::Overflow, H256}; +use tendermint::account::Id as TendermintAccountId; +use tendermint::public_key::PublicKey as TendermintPublicKey; + +/// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding +#[derive(new, Debug)] +pub struct CosmosAddress { + /// Bech32 encoded cosmos account + account_id: AccountId, + /// Hex representation (digest) of cosmos account + digest: H256, +} + +impl CosmosAddress { + /// Returns a Bitcoin style address: RIPEMD160(SHA256(pubkey)) + /// Source: https://github.com/cosmos/cosmos-sdk/blob/177e7f45959215b0b4e85babb7c8264eaceae052/crypto/keys/secp256k1/secp256k1.go#L154 + pub fn from_pubkey(pubkey: PublicKey, prefix: &str) -> ChainResult { + // Get the inner type + let tendermint_pubkey = TendermintPublicKey::from(pubkey); + // Get the RIPEMD160(SHA256(pubkey)) + let tendermint_id = TendermintAccountId::from(tendermint_pubkey); + // Bech32 encoding + let account_id = AccountId::new(prefix, tendermint_id.as_bytes())?; + // Hex digest + let digest = Self::bech32_decode(account_id.clone())?; + Ok(CosmosAddress::new(account_id, digest)) + } + + /// Creates a wrapper arround a cosmrs AccountId from a private key byte array + pub fn from_privkey(priv_key: &[u8], prefix: &str) -> ChainResult { + let pubkey = SigningKey::from_slice(priv_key)?.public_key(); + Self::from_pubkey(pubkey, prefix) + } + + /// Creates a wrapper arround a cosmrs AccountId from a H256 digest + /// + /// - digest: H256 digest (hex representation of address) + /// - prefix: Bech32 prefix + pub fn from_h256(digest: H256, prefix: &str) -> ChainResult { + // This is the hex-encoded version of the address + let bytes = digest.as_bytes(); + // Bech32 encode it + let account_id = AccountId::new(prefix, bytes)?; + Ok(CosmosAddress::new(account_id, digest)) + } + + /// Builds a H256 digest from a cosmos AccountId (Bech32 encoding) + fn bech32_decode(account_id: AccountId) -> ChainResult { + // Temporarily set the digest to a default value as a placeholder. + // Can't implement H256::try_from for AccountId to avoid this. + let cosmos_address = CosmosAddress::new(account_id, Default::default()); + H256::try_from(&cosmos_address) + } + + /// String representation of a cosmos AccountId + pub fn address(&self) -> String { + self.account_id.to_string() + } + + /// H256 digest of the cosmos AccountId + pub fn digest(&self) -> H256 { + self.digest + } +} + +impl TryFrom<&CosmosAddress> for H256 { + type Error = ChainCommunicationError; + + fn try_from(cosmos_address: &CosmosAddress) -> Result { + // `to_bytes()` decodes the Bech32 into a hex, represented as a byte vec + let bytes = cosmos_address.account_id.to_bytes(); + let h256_len = H256::len_bytes(); + let Some(start_point) = h256_len.checked_sub(bytes.len()) else { + // input is too large to fit in a H256 + return Err(Overflow.into()); + }; + let mut empty_hash = H256::default(); + let result = empty_hash.as_bytes_mut(); + result[start_point..].copy_from_slice(bytes.as_slice()); + Ok(H256::from_slice(result)) + } +} + +impl FromStr for CosmosAddress { + type Err = ChainCommunicationError; + + fn from_str(s: &str) -> Result { + let account_id = AccountId::from_str(s)?; + let digest = Self::bech32_decode(account_id.clone())?; + Ok(Self::new(account_id, digest)) + } +} + +#[cfg(test)] +pub mod test { + use hyperlane_core::utils::hex_or_base58_to_h256; + + use super::*; + + #[test] + fn test_bech32_decode() { + let addr = "dual1pk99xge6q94qtu3568x3qhp68zzv0mx7za4ct008ks36qhx5tvss3qawfh"; + let cosmos_address = CosmosAddress::from_str(addr).unwrap(); + assert_eq!( + cosmos_address.digest, + H256::from_str("0d8a53233a016a05f234d1cd105c3a3884c7ecde176b85bde7b423a05cd45b21") + .unwrap() + ); + } + + #[test] + fn test_bech32_decode_from_cosmos_key() { + let hex_key = "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26"; + let key = hex_or_base58_to_h256(hex_key).unwrap(); + let prefix = "neutron"; + let addr = CosmosAddress::from_privkey(key.as_bytes(), prefix) + .expect("Cosmos address creation failed"); + assert_eq!( + addr.address(), + "neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj" + ); + } + + #[test] + fn test_bech32_encode_from_h256() { + let hex_key = "0x1b16866227825a5166eb44031cdcf6568b3e80b52f2806e01b89a34dc90ae616"; + let key = hex_or_base58_to_h256(hex_key).unwrap(); + let prefix = "dual"; + let addr = CosmosAddress::from_h256(key, prefix).expect("Cosmos address creation failed"); + assert_eq!( + addr.address(), + "dual1rvtgvc38sfd9zehtgsp3eh8k269naq949u5qdcqm3x35mjg2uctqfdn3yq" + ); + } +} diff --git a/rust/chains/hyperlane-cosmos/src/libs/binary.rs b/rust/chains/hyperlane-cosmos/src/libs/binary.rs deleted file mode 100644 index a75ffaa342..0000000000 --- a/rust/chains/hyperlane-cosmos/src/libs/binary.rs +++ /dev/null @@ -1,28 +0,0 @@ -use hyperlane_core::{H160, H256, H512}; -use std::cmp; - -/// Convert H256 to H512 -pub fn h256_to_h512(v: H256) -> H512 { - let mut result: [u8; 64] = [0; 64]; - let vec = v.0.as_slice(); - let start_point = cmp::max(0, 32 - vec.len()); - result[start_point..32].copy_from_slice(vec); - - H512::from_slice(&result) -} - -/// Convert H256 to H160 -pub fn h256_to_h160(v: H256) -> H160 { - let mut result = [0u8; 20]; - - result.copy_from_slice(&v.0[12..]); - H160::from_slice(&result) -} - -/// Convert H160 to H256 -pub fn h160_to_h256(v: H160) -> H256 { - let mut result = [0u8; 32]; - result[12..].copy_from_slice(v.as_bytes()); - - H256::from_slice(&result) -} diff --git a/rust/chains/hyperlane-cosmos/src/libs/mod.rs b/rust/chains/hyperlane-cosmos/src/libs/mod.rs index 6c33ef5122..d89e6cd7db 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/mod.rs @@ -1,5 +1,2 @@ /// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain. -pub mod verify; - -/// This module contains all the Binary variables used by the Hyperlane Cosmos chain. -pub mod binary; +pub mod address; diff --git a/rust/chains/hyperlane-cosmos/src/libs/verify.rs b/rust/chains/hyperlane-cosmos/src/libs/verify.rs deleted file mode 100644 index 8fe0fba28a..0000000000 --- a/rust/chains/hyperlane-cosmos/src/libs/verify.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::cmp; - -use bech32::{FromBase32, ToBase32}; -use cosmrs::crypto::secp256k1::SigningKey; -use hyperlane_core::{ChainCommunicationError, ChainResult, H160, H256}; -use ripemd::Ripemd160; -use sha2::{Digest, Sha256}; - -use crate::HyperlaneCosmosError; - -/// decode bech32 address to H256 -pub fn bech32_decode(addr: String) -> ChainResult { - let (_hrp, data, _variant) = - bech32::decode(addr.as_str()).map_err(Into::::into)?; - - let value = Vec::::from_base32(&data).map_err(Into::::into)?; - let mut result: [u8; 32] = [0; 32]; - - let start_point = cmp::max(0, 32 - value.len()); - result[start_point..32].copy_from_slice(value.as_slice()); - - Ok(H256::from(result)) -} - -/// encode H256 to bech32 address -pub fn digest_to_addr(digest: H256, prefix: &str) -> ChainResult { - let addr = bech32::encode( - prefix, - digest.as_bytes().to_base32(), - bech32::Variant::Bech32, - ) - .map_err(|_| ChainCommunicationError::InvalidRequest { - msg: "invalid address".to_string(), - })?; - - Ok(addr) -} - -/// encode H256 to bech32 address -pub fn sha256_digest(bz: impl AsRef<[u8]>) -> ChainResult<[u8; 32]> { - let mut hasher = Sha256::new(); - - hasher.update(bz); - - hasher - .finalize() - .as_slice() - .try_into() - .map_err(|_| ChainCommunicationError::ParseError { - msg: "sha256 digest".to_string(), - }) -} - -/// encode H256 to bech32 address -pub fn ripemd160_digest(bz: impl AsRef<[u8]>) -> ChainResult<[u8; 20]> { - let mut hasher = Ripemd160::new(); - - hasher.update(bz); - - hasher - .finalize() - .as_slice() - .try_into() - .map_err(|_| ChainCommunicationError::ParseError { - msg: "ripemd160".to_string(), - }) -} - -/// encode H256 to bech32 address -pub fn pub_to_addr(pub_key: Vec, prefix: &str) -> ChainResult { - let sha_hash = sha256_digest(pub_key)?; - let rip_hash = ripemd160_digest(sha_hash)?; - - let addr = - bech32::encode(prefix, rip_hash.to_base32(), bech32::Variant::Bech32).map_err(|_| { - ChainCommunicationError::ParseError { - msg: "bech32".to_string(), - } - })?; - - Ok(addr) -} - -/// encode H256 to bech32 address -pub fn priv_to_binary_addr(priv_key: Vec) -> ChainResult { - let sha_hash = sha256_digest( - SigningKey::from_slice(priv_key.as_slice())? - .public_key() - .to_bytes(), - )?; - let rip_hash = ripemd160_digest(sha_hash)?; - - Ok(H160::from_slice(rip_hash.as_slice())) -} - -/// encode H256 to bech32 address -pub fn priv_to_addr_string(prefix: String, priv_key: Vec) -> ChainResult { - let sha_hash = sha256_digest( - SigningKey::from_slice(priv_key.as_slice())? - .public_key() - .to_bytes(), - )?; - let rip_hash = ripemd160_digest(sha_hash)?; - - let addr = - bech32::encode(&prefix, rip_hash.to_base32(), bech32::Variant::Bech32).map_err(|_| { - ChainCommunicationError::ParseError { - msg: "bech32".to_string(), - } - })?; - - Ok(addr) -} - -/// encode H256 to bech32 address -pub fn pub_to_binary_addr(pub_key: Vec) -> ChainResult { - let sha_hash = match (pub_key.len() == 33) && (pub_key[0] == 0x02 || pub_key[0] == 0x03) { - true => sha256_digest(pub_key)?, - false => { - let comp_pub_key = vec![0x03]; - let comp_pub_key = comp_pub_key - .into_iter() - .chain(pub_key[1..33].iter().cloned()) - .collect::>(); - - sha256_digest(comp_pub_key)? - } - }; - let rip_hash = ripemd160_digest(sha_hash)?; - - Ok(H160::from_slice(rip_hash.as_slice())) -} diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index 301e212a78..e0bfe8c565 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -4,8 +4,10 @@ use std::{ io::Cursor, num::NonZeroU64, ops::RangeInclusive, + str::FromStr, }; +use crate::address::CosmosAddress; use crate::grpc::{WasmGrpcProvider, WasmProvider}; use crate::payloads::mailbox::{ GeneralMailboxQuery, ProcessMessageRequest, ProcessMessageRequestInner, @@ -13,17 +15,14 @@ use crate::payloads::mailbox::{ use crate::payloads::{general, mailbox}; use crate::rpc::{CosmosWasmIndexer, ParsedEvent, WasmIndexer}; use crate::CosmosProvider; -use crate::{signers::Signer, utils::get_block_height_for_lag, verify, ConnectionConf}; +use crate::{signers::Signer, utils::get_block_height_for_lag, ConnectionConf}; use async_trait::async_trait; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse; use cosmrs::tendermint::abci::EventAttribute; use once_cell::sync::Lazy; -use crate::{ - binary::h256_to_h512, - utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}, -}; +use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; use hyperlane_core::{ utils::fmt_bytes, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox, TxCostEstimate, TxOutcome, @@ -137,7 +136,7 @@ impl Mailbox for CosmosMailbox { #[instrument(err, ret, skip(self))] async fn recipient_ism(&self, recipient: H256) -> ChainResult { - let address = verify::digest_to_addr(recipient, &self.signer.prefix)?; + let address = CosmosAddress::from_h256(recipient, &self.signer.prefix)?.address(); let payload = mailbox::RecipientIsmRequest { recipient_ism: mailbox::RecipientIsmRequestInner { @@ -152,8 +151,8 @@ impl Mailbox for CosmosMailbox { let response: mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?; // convert Hex to H256 - let ism = verify::bech32_decode(response.ism)?; - Ok(ism) + let ism = CosmosAddress::from_str(&response.ism)?; + Ok(ism.digest()) } #[instrument(err, ret, skip(self))] @@ -175,9 +174,7 @@ impl Mailbox for CosmosMailbox { .wasm_send(process_message, tx_gas_limit) .await?; Ok(TxOutcome { - transaction_id: h256_to_h512(H256::from_slice( - hex::decode(response.txhash)?.as_slice(), - )), + transaction_id: H256::from_slice(hex::decode(response.txhash)?.as_slice()).into(), executed: response.code == 0, gas_used: U256::from(response.gas_used), gas_price: U256::from(response.gas_wanted), diff --git a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs index 83d6f6646d..7277c7737e 100644 --- a/rust/chains/hyperlane-cosmos/src/multisig_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/multisig_ism.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use crate::{ - binary::h160_to_h256, grpc::{WasmGrpcProvider, WasmProvider}, payloads::ism_routes::QueryIsmGeneralRequest, signers::Signer, @@ -78,7 +77,7 @@ impl MultisigIsm for CosmosMultisigIsm { let validators: ChainResult> = response .validators .iter() - .map(|v| H160::from_str(v).map(h160_to_h256).map_err(Into::into)) + .map(|v| H160::from_str(v).map(H256::from).map_err(Into::into)) .collect(); Ok((validators?, response.threshold)) diff --git a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs index 021033e0b2..a39db5a8b0 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -24,8 +24,9 @@ use hyperlane_core::{ }; use serde::Serialize; +use crate::address::CosmosAddress; +use crate::HyperlaneCosmosError; use crate::{signers::Signer, ConnectionConf}; -use crate::{verify, HyperlaneCosmosError}; const DEFAULT_GAS_PRICE: f32 = 0.05; const DEFAULT_GAS_ADJUSTMENT: f32 = 1.25; @@ -106,7 +107,8 @@ impl WasmGrpcProvider { } fn get_contract_addr(&self) -> ChainResult { - verify::digest_to_addr(self.address, self.signer.prefix.as_str()) + let cosmos_address = CosmosAddress::from_h256(self.address, &self.conf.get_prefix())?; + Ok(cosmos_address.address()) } } diff --git a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs index cbed71afdb..111ed4881d 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/rpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/rpc.rs @@ -1,6 +1,5 @@ use std::ops::RangeInclusive; -use crate::binary::h256_to_h512; use async_trait::async_trait; use cosmrs::rpc::client::{Client, CompatMode, HttpClient}; use cosmrs::rpc::endpoint::{tx, tx_search::Response as TxSearchResponse}; @@ -10,7 +9,7 @@ use cosmrs::tendermint::abci::EventAttribute; use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, LogMeta, H256, U256}; use tracing::{instrument, trace}; -use crate::verify::digest_to_addr; +use crate::address::CosmosAddress; use crate::ConnectionConf; const PAGINATION_LIMIT: u8 = 100; @@ -28,17 +27,17 @@ pub trait WasmIndexer: Send + Sync { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult> where - T: Send + Sync + 'static; + T: Send + Sync + PartialEq + 'static; } #[derive(Debug, Eq, PartialEq)] /// An event parsed from the RPC response. -pub struct ParsedEvent { +pub struct ParsedEvent { contract_address: String, event: T, } -impl ParsedEvent { +impl ParsedEvent { /// Create a new ParsedEvent. pub fn new(contract_address: String, event: T) -> Self { Self { @@ -52,8 +51,7 @@ impl ParsedEvent { /// Cosmwasm RPC Provider pub struct CosmosWasmIndexer { client: HttpClient, - contract_address: H256, - contract_address_bech32: String, + contract_address: CosmosAddress, target_event_kind: String, reorg_period: u32, } @@ -74,8 +72,10 @@ impl CosmosWasmIndexer { .build()?; Ok(Self { client, - contract_address: locator.address, - contract_address_bech32: digest_to_addr(locator.address, conf.get_prefix().as_str())?, + contract_address: CosmosAddress::from_h256( + locator.address, + conf.get_prefix().as_str(), + )?, target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), reorg_period, }) @@ -99,7 +99,7 @@ impl CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult + '_> where - T: 'static, + T: PartialEq + 'static, { let logs_iter = txs .into_iter() @@ -127,7 +127,7 @@ impl CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> impl Iterator + '_ where - T: 'static, + T: PartialEq + 'static, { tx.tx_result.events.into_iter().enumerate().filter_map(move |(log_idx, event)| { if event.kind.as_str() != self.target_event_kind { @@ -146,18 +146,18 @@ impl CosmosWasmIndexer { // in the event matches the contract address we are indexing. // Otherwise, we might index events from other contracts that happen // to have the same target event name. - if parsed_event.contract_address != self.contract_address_bech32 { + if parsed_event.contract_address != self.contract_address.address() { trace!(tx_hash=?tx.hash, log_idx, ?event, "Event contract address does not match indexer contract address"); return None; } Some((parsed_event.event, LogMeta { - address: self.contract_address, + address: self.contract_address.digest(), block_number: tx.height.value(), // FIXME: block_hash is not available in tx_search. // This isn't strictly required atm. block_hash: H256::zero(), - transaction_id: h256_to_h512(H256::from_slice(tx.hash.as_bytes())), + transaction_id: H256::from_slice(tx.hash.as_bytes()).into(), transaction_index: tx.index.into(), log_index: U256::from(log_idx), })) @@ -189,7 +189,7 @@ impl WasmIndexer for CosmosWasmIndexer { parser: for<'a> fn(&'a Vec) -> ChainResult>, ) -> ChainResult> where - T: Send + Sync + 'static, + T: PartialEq + Send + Sync + 'static, { // Page starts from 1 let query = Query::default() @@ -197,7 +197,7 @@ impl WasmIndexer for CosmosWasmIndexer { .and_lte("tx.height", *range.end() as u64) .and_eq( format!("{}._contract_address", self.target_event_kind), - self.contract_address_bech32.clone(), + self.contract_address.address(), ); let tx_search_result = self.tx_search(query.clone(), 1).await?; diff --git a/rust/chains/hyperlane-cosmos/src/routing_ism.rs b/rust/chains/hyperlane-cosmos/src/routing_ism.rs index 7f96175664..497aedcc87 100644 --- a/rust/chains/hyperlane-cosmos/src/routing_ism.rs +++ b/rust/chains/hyperlane-cosmos/src/routing_ism.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use async_trait::async_trait; use hyperlane_core::{ @@ -6,12 +8,12 @@ use hyperlane_core::{ }; use crate::{ + address::CosmosAddress, grpc::{WasmGrpcProvider, WasmProvider}, payloads::ism_routes::{ IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest, }, signers::Signer, - verify::bech32_decode, ConnectionConf, CosmosProvider, }; @@ -72,6 +74,6 @@ impl RoutingIsm for CosmosRoutingIsm { .await?; let response: IsmRouteRespnose = serde_json::from_slice(&data)?; - Ok(bech32_decode(response.ism)?) + Ok(CosmosAddress::from_str(&response.ism)?.digest()) } } diff --git a/rust/chains/hyperlane-cosmos/src/signers.rs b/rust/chains/hyperlane-cosmos/src/signers.rs index e07de304e1..0194bb5452 100644 --- a/rust/chains/hyperlane-cosmos/src/signers.rs +++ b/rust/chains/hyperlane-cosmos/src/signers.rs @@ -1,7 +1,7 @@ use cosmrs::crypto::{secp256k1::SigningKey, PublicKey}; use hyperlane_core::ChainResult; -use crate::verify; +use crate::address::CosmosAddress; #[derive(Clone, Debug)] /// Signer for cosmos chain @@ -23,10 +23,8 @@ impl Signer { /// * `private_key` - private key for signer /// * `prefix` - prefix for signer address pub fn new(private_key: Vec, prefix: String) -> ChainResult { - let address = Self::address(&private_key, &prefix)?; - + let address = CosmosAddress::from_privkey(&private_key, &prefix)?.address(); let signing_key = Self::build_signing_key(&private_key)?; - SigningKey::from_slice(&private_key)?; let public_key = signing_key.public_key(); Ok(Self { public_key, @@ -36,17 +34,6 @@ impl Signer { }) } - /// get bech32 address - fn address(private_key: &Vec, prefix: &str) -> ChainResult { - let address = verify::pub_to_addr( - SigningKey::from_slice(private_key.as_slice())? - .public_key() - .to_bytes(), - prefix, - )?; - Ok(address) - } - /// Build a SigningKey from a private key. This cannot be /// precompiled and stored in `Signer`, because `SigningKey` is not `Sync`. pub fn signing_key(&self) -> ChainResult { diff --git a/rust/chains/hyperlane-cosmos/src/types.rs b/rust/chains/hyperlane-cosmos/src/types.rs new file mode 100644 index 0000000000..d2e7c965ad --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/types.rs @@ -0,0 +1,24 @@ +use hyperlane_core::ModuleType; + +pub struct IsmType(pub hpl_interface::ism::ISMType); + +impl From for IsmType { + fn from(value: hpl_interface::ism::ISMType) -> Self { + IsmType(value) + } +} + +impl From for ModuleType { + fn from(value: IsmType) -> Self { + match value.0 { + hpl_interface::ism::ISMType::Unused => ModuleType::Unused, + hpl_interface::ism::ISMType::Routing => ModuleType::Routing, + hpl_interface::ism::ISMType::Aggregation => ModuleType::Aggregation, + hpl_interface::ism::ISMType::LegacyMultisig => ModuleType::MessageIdMultisig, + hpl_interface::ism::ISMType::MerkleRootMultisig => ModuleType::MerkleRootMultisig, + hpl_interface::ism::ISMType::MessageIdMultisig => ModuleType::MessageIdMultisig, + hpl_interface::ism::ISMType::Null => ModuleType::Null, + hpl_interface::ism::ISMType::CcipRead => ModuleType::CcipRead, + } + } +} diff --git a/rust/chains/hyperlane-cosmos/src/validator_announce.rs b/rust/chains/hyperlane-cosmos/src/validator_announce.rs index dc59c8c0bb..59a98a7462 100644 --- a/rust/chains/hyperlane-cosmos/src/validator_announce.rs +++ b/rust/chains/hyperlane-cosmos/src/validator_announce.rs @@ -4,11 +4,10 @@ use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use hyperlane_core::{ Announcement, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, - ValidatorAnnounce, H256, U256, + ValidatorAnnounce, H160, H256, U256, }; use crate::{ - binary::h256_to_h160, grpc::{WasmGrpcProvider, WasmProvider}, payloads::validator_announce::{ self, AnnouncementRequest, AnnouncementRequestInner, GetAnnounceStorageLocationsRequest, @@ -67,7 +66,7 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { ) -> ChainResult>> { let vss = validators .iter() - .map(|v| h256_to_h160(*v)) + .map(|v| H160::from(*v)) .map(|v| hex::encode(v.as_bytes())) .collect::>(); diff --git a/rust/hyperlane-base/src/settings/chains.rs b/rust/hyperlane-base/src/settings/chains.rs index 338bccab67..8ca4a1cda1 100644 --- a/rust/hyperlane-base/src/settings/chains.rs +++ b/rust/hyperlane-base/src/settings/chains.rs @@ -149,7 +149,7 @@ impl ChainConf { Ok(Box::new(h_cosmos::CosmosMailbox::new( conf.clone(), locator.clone(), - signer.clone().unwrap(), + signer.clone().expect("Cosmos signer not configured"), )) as Box) } } diff --git a/rust/hyperlane-core/src/error.rs b/rust/hyperlane-core/src/error.rs index 4faab8c4d1..42a150aa6c 100644 --- a/rust/hyperlane-core/src/error.rs +++ b/rust/hyperlane-core/src/error.rs @@ -8,6 +8,7 @@ use cosmrs::proto::prost; use cosmrs::Error as CosmrsError; use std::string::FromUtf8Error; +use crate::Error as PrimitiveTypeError; use crate::HyperlaneProviderError; use crate::H256; @@ -142,6 +143,9 @@ pub enum ChainCommunicationError { /// Failed to estimate transaction gas cost. #[error("Failed to estimate transaction gas cost {0}")] TxCostEstimateError(String), + /// Primitive type error + #[error(transparent)] + PrimitiveTypeError(#[from] PrimitiveTypeError), } impl ChainCommunicationError { diff --git a/rust/hyperlane-core/src/traits/signing.rs b/rust/hyperlane-core/src/traits/signing.rs index f2d1691af6..81f1c348b2 100644 --- a/rust/hyperlane-core/src/traits/signing.rs +++ b/rust/hyperlane-core/src/traits/signing.rs @@ -1,19 +1,20 @@ -use std::fmt::{Debug, Formatter}; - use async_trait::async_trait; use auto_impl::auto_impl; -use elliptic_curve::consts::U32; -#[cfg(feature = "ethers")] -use ethers_core::k256::{ - ecdsa::recoverable::Signature as RecoverableSignature, ecdsa::Signature as K256Signature, - PublicKey as K256PublicKey, -}; -use generic_array::GenericArray; - use serde::{ ser::{SerializeStruct, Serializer}, Deserialize, Serialize, }; +use std::fmt::{Debug, Formatter}; + +#[cfg(feature = "ethers")] +use { + elliptic_curve::consts::U32, + ethers_core::k256::{ + ecdsa::recoverable::Signature as RecoverableSignature, ecdsa::Signature as K256Signature, + PublicKey as K256PublicKey, + }, + generic_array::GenericArray, +}; use crate::utils::fmt_bytes; use crate::{Signature, H160, H256}; diff --git a/rust/hyperlane-core/src/types/primitive_types.rs b/rust/hyperlane-core/src/types/primitive_types.rs index 58dd9bffab..2b0f5112e6 100644 --- a/rust/hyperlane-core/src/types/primitive_types.rs +++ b/rust/hyperlane-core/src/types/primitive_types.rs @@ -10,9 +10,10 @@ use uint::construct_uint; use crate::types::serialize; /// Error type for conversion. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum Error { /// Overflow encountered. + #[error("Overflow when creating primitive type")] Overflow, } diff --git a/rust/hyperlane-core/src/utils.rs b/rust/hyperlane-core/src/utils.rs index 41e9c115d0..56a3e4c9b3 100644 --- a/rust/hyperlane-core/src/utils.rs +++ b/rust/hyperlane-core/src/utils.rs @@ -1,7 +1,9 @@ -use std::{str::FromStr, time::Duration}; - use eyre::Result; use sha3::{digest::Update, Digest, Keccak256}; +use std::str::FromStr; + +#[cfg(feature = "float")] +use std::time::Duration; use crate::{KnownHyperlaneDomain, H160, H256}; diff --git a/rust/utils/run-locally/Cargo.toml b/rust/utils/run-locally/Cargo.toml index 04b272658b..1f59fb2fb3 100644 --- a/rust/utils/run-locally/Cargo.toml +++ b/rust/utils/run-locally/Cargo.toml @@ -10,6 +10,7 @@ publish.workspace = true version.workspace = true [dependencies] +hyperlane-core = { path = "../../hyperlane-core" } toml_edit.workspace = true k256.workspace = true ripemd.workspace = true @@ -26,6 +27,5 @@ ureq = { workspace = true, default-features = false } which.workspace = true macro_rules_attribute.workspace = true regex.workspace = true -hyperlane-core = { path = "../../hyperlane-core" } hpl-interface.workspace = true cosmwasm-schema.workspace = true diff --git a/rust/utils/run-locally/src/cosmos/crypto.rs b/rust/utils/run-locally/src/cosmos/crypto.rs index b583ee3b89..e4520cbcbf 100644 --- a/rust/utils/run-locally/src/cosmos/crypto.rs +++ b/rust/utils/run-locally/src/cosmos/crypto.rs @@ -1,3 +1,7 @@ +// TODO: this file can be removed if `CosmosAddress` can be imported from `hyperlane-cosmos`. +// However, adding a hyperlane-cosmos dep creates a dep cycle. +// Look into how this can be fixed. + use k256::ecdsa::{SigningKey, VerifyingKey}; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; diff --git a/rust/utils/run-locally/src/cosmos/link.rs b/rust/utils/run-locally/src/cosmos/link.rs index 46bc5cabcc..1e65aa4b25 100644 --- a/rust/utils/run-locally/src/cosmos/link.rs +++ b/rust/utils/run-locally/src/cosmos/link.rs @@ -92,7 +92,6 @@ fn link_network( target_domain: u32, ) { let validator_addr = validator.addr(hrp); - let _validator_pubkey = validator.pub_key_to_binary(); let dest_domain = if network.domain == 26657 { 26658