diff --git a/rust/chains/hyperlane-cosmos/src/libs/account.rs b/rust/chains/hyperlane-cosmos/src/libs/account.rs new file mode 100644 index 000000000..4037ba317 --- /dev/null +++ b/rust/chains/hyperlane-cosmos/src/libs/account.rs @@ -0,0 +1,41 @@ +use cosmrs::AccountId; + +use hyperlane_core::Error::Overflow; +use hyperlane_core::{ChainCommunicationError, H256}; + +pub(crate) struct CosmosAccountId<'a> { + account_id: &'a AccountId, +} + +impl<'a> CosmosAccountId<'a> { + pub fn new(account_id: &'a AccountId) -> Self { + Self { account_id } + } +} + +impl TryFrom<&CosmosAccountId<'_>> for H256 { + type Error = ChainCommunicationError; + + /// Builds a H256 digest from a cosmos AccountId (Bech32 encoding) + fn try_from(account_id: &CosmosAccountId) -> Result { + let bytes = account_id.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 TryFrom> for H256 { + type Error = ChainCommunicationError; + + /// Builds a H256 digest from a cosmos AccountId (Bech32 encoding) + fn try_from(account_id: CosmosAccountId) -> Result { + (&account_id).try_into() + } +} diff --git a/rust/chains/hyperlane-cosmos/src/libs/address.rs b/rust/chains/hyperlane-cosmos/src/libs/address.rs index e594cb460..2d57385ca 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/address.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/address.rs @@ -9,6 +9,7 @@ use hyperlane_core::{ChainCommunicationError, ChainResult, Error::Overflow, H256 use tendermint::account::Id as TendermintAccountId; use tendermint::public_key::PublicKey as TendermintPublicKey; +use crate::libs::account::CosmosAccountId; use crate::HyperlaneCosmosError; /// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding @@ -32,7 +33,7 @@ impl CosmosAddress { let account_id = AccountId::new(prefix, tendermint_id.as_bytes()) .map_err(Into::::into)?; // Hex digest - let digest = Self::bech32_decode(account_id.clone())?; + let digest = H256::try_from(&CosmosAccountId::new(&account_id))?; Ok(CosmosAddress::new(account_id, digest)) } @@ -69,14 +70,6 @@ impl CosmosAddress { 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() @@ -92,17 +85,7 @@ 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)) + CosmosAccountId::new(&cosmos_address.account_id).try_into() } } @@ -111,7 +94,7 @@ impl FromStr for CosmosAddress { fn from_str(s: &str) -> Result { let account_id = AccountId::from_str(s).map_err(Into::::into)?; - let digest = Self::bech32_decode(account_id.clone())?; + let digest = CosmosAccountId::new(&account_id).try_into()?; Ok(Self::new(account_id, digest)) } } diff --git a/rust/chains/hyperlane-cosmos/src/libs/mod.rs b/rust/chains/hyperlane-cosmos/src/libs/mod.rs index d89e6cd7d..d641b041a 100644 --- a/rust/chains/hyperlane-cosmos/src/libs/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/libs/mod.rs @@ -1,2 +1,4 @@ +/// This module contains conversions from Cosmos AccountId to H56 +pub(crate) mod account; /// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain. pub mod address; diff --git a/rust/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/chains/hyperlane-cosmos/src/providers/mod.rs index 13f84df8f..da29de1b1 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/mod.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; +use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::crypto::PublicKey; +use cosmrs::tx::{MessageExt, SignerInfo}; use cosmrs::Tx; use tendermint::hash::Algorithm; use tendermint::Hash; @@ -13,6 +15,7 @@ use hyperlane_core::{ use crate::address::CosmosAddress; use crate::grpc::WasmProvider; +use crate::libs::account::CosmosAccountId; use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; use self::grpc::WasmGrpcProvider; @@ -74,6 +77,18 @@ impl CosmosProvider { pub fn rpc(&self) -> &HttpClient { &self.rpc_client } + + fn sender(&self, signer: &SignerInfo) -> Result { + let signer_public_key = signer + .public_key + .clone() + .ok_or_else(|| ChainCommunicationError::from_other_str("no public key"))?; + let public_key: PublicKey = signer_public_key.try_into()?; + let key = + CosmosAddress::from_pubkey(public_key, &self.connection_conf.get_bech32_prefix())?; + let sender = key.digest(); + Ok(sender) + } } impl HyperlaneChain for CosmosProvider { @@ -145,34 +160,51 @@ impl HyperlaneProvider for CosmosProvider { ChainCommunicationError::from_other_str("could not deserialize transaction") })?; + // TODO assuming that there is only one message in the transaction and it is execute contract message + let any = tx.body.messages.get(0).unwrap(); + let proto = + cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContract::from_any(any).map_err(|e| { + ChainCommunicationError::from_other_str( + "could not decode contract execution message", + ) + })?; + let msg = MsgExecuteContract::try_from(proto) + .map_err(|e| ChainCommunicationError::from_other_str("could not convert from proto"))?; + let contract = H256::try_from(CosmosAccountId::new(&msg.contract))?; + + // TODO choose correct signer (should it be the one paid for the transaction) let signer = tx .auth_info .signer_infos .get(0) .expect("there should be at least one signer"); - let signer_public_key = signer - .public_key - .clone() - .ok_or_else(|| ChainCommunicationError::from_other_str("no public key"))?; + let sender = self.sender(signer)?; + let nonce = signer.sequence; - let public_key: PublicKey = signer_public_key.try_into()?; - let key = - CosmosAddress::from_pubkey(public_key, &self.connection_conf.get_bech32_prefix())?; - let sender = key.digest(); + // TODO support multiple denomination for amount + let gas_limit = U256::from(tx.auth_info.fee.gas_limit); + let fee = tx + .auth_info + .fee + .amount + .iter() + .fold(U256::zero(), |acc, a| acc + a.amount); + + let gas_price = fee / gas_limit; let tx_info = TxnInfo { hash: hash.to_owned(), gas_limit: U256::from(response.tx_result.gas_wanted), max_priority_fee_per_gas: None, max_fee_per_gas: None, - gas_price: None, - nonce: 0, + gas_price: Some(gas_price), + nonce, sender, - recipient: None, + recipient: Some(contract), receipt: Some(TxnReceiptInfo { gas_used: U256::from(response.tx_result.gas_used), cumulative_gas_used: U256::from(response.tx_result.gas_used), - effective_gas_price: None, + effective_gas_price: Some(gas_price), }), };