diff --git a/rust/chains/hyperlane-cosmos/src/error.rs b/rust/chains/hyperlane-cosmos/src/error.rs index d266d317c..abe61fd92 100644 --- a/rust/chains/hyperlane-cosmos/src/error.rs +++ b/rust/chains/hyperlane-cosmos/src/error.rs @@ -47,6 +47,12 @@ pub enum HyperlaneCosmosError { /// Fallback providers failed #[error("Fallback providers failed. (Errors: {0:?})")] FallbackProvidersFailed(Vec), + /// Public key error + #[error("{0}")] + PublicKeyError(String), + /// Signer info error + #[error("{0}")] + SignerInfoError(String), } impl From for ChainCommunicationError { diff --git a/rust/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/chains/hyperlane-cosmos/src/providers/mod.rs index 4f0026278..f033b313c 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/mod.rs @@ -83,7 +83,7 @@ impl CosmosProvider { &self, signer_infos: &[SignerInfo], payer: &AccountId, - ) -> Result<(AccountId, SequenceNumber), ChainCommunicationError> { + ) -> ChainResult<(AccountId, SequenceNumber)> { signer_infos .iter() .map(|si| self.convert_signer_info_into_account_id_and_nonce(si)) @@ -102,9 +102,9 @@ impl CosmosProvider { fn convert_signer_info_into_account_id_and_nonce( &self, signer_info: &SignerInfo, - ) -> Result<(AccountId, SequenceNumber), ChainCommunicationError> { + ) -> ChainResult<(AccountId, SequenceNumber)> { let signer_public_key = signer_info.public_key.clone().ok_or_else(|| { - ChainCommunicationError::from_other_str("no public key for default signer") + HyperlaneCosmosError::PublicKeyError("no public key for default signer".to_owned()) })?; let public_key = PublicKey::try_from(signer_public_key)?; @@ -116,6 +116,53 @@ impl CosmosProvider { Ok((account_id, signer_info.sequence)) } + + /// Calculates the sender and the nonce for the transaction. + /// We use `payer` of the fees as the sender of the transaction, and we search for `payer` + /// signature information to find the nonce. + /// If `payer` is not specified, we use the account which signed the transaction first, as + /// the sender. + fn sender_and_nonce(&self, tx: &Tx) -> ChainResult<(H256, SequenceNumber)> { + let (sender, nonce) = tx + .auth_info + .fee + .payer + .as_ref() + .map(|payer| self.search_payer_in_signer_infos(&tx.auth_info.signer_infos, payer)) + .map_or_else( + || { + let signer_info = tx.auth_info.signer_infos.get(0).ok_or_else(|| { + HyperlaneCosmosError::SignerInfoError( + "no signer info in default signer".to_owned(), + ) + })?; + self.convert_signer_info_into_account_id_and_nonce(signer_info) + }, + |p| p, + ) + .map(|(a, n)| CosmosAddress::from_account_id(a).map(|a| (a.digest(), n)))??; + Ok((sender, nonce)) + } + + /// Extract contract address from transaction. + /// Assumes that there is only one `MsgExecuteContract` message in the transaction + fn contract(tx: &Tx) -> ChainResult { + use cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContract as ProtoMsgExecuteContract; + + let any = tx + .body + .messages + .iter() + .find(|a| a.type_url == "/cosmwasm.wasm.v1.MsgExecuteContract") + .ok_or_else(|| { + ChainCommunicationError::from_other_str("could not find contract execution message") + })?; + let proto = + ProtoMsgExecuteContract::from_any(any).map_err(Into::::into)?; + let msg = MsgExecuteContract::try_from(proto)?; + let contract = H256::try_from(CosmosAccountId::new(&msg.contract))?; + Ok(contract) + } } impl HyperlaneChain for CosmosProvider { @@ -170,55 +217,25 @@ impl HyperlaneProvider for CosmosProvider { let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) .expect("transaction hash should be of correct size"); - // TODO add proper error handling let response = self .rpc_client .tx(tendermint_hash, false) .await - .map_err(|_| ChainCommunicationError::from_other_str("generic error"))?; + .map_err(Into::::into)?; let received_hash = H256::from_slice(response.hash.as_bytes()); if &received_hash != hash { - return Err(ChainCommunicationError::from_other_str( - "received incorrect transaction", - )); + return Err(ChainCommunicationError::from_other_str(&format!( + "received incorrect transaction, expected hash: {:?}, received hash: {:?}", + hash, received_hash, + ))); } - let tx = Tx::from_bytes(&response.tx).map_err(|_| { - 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))?; + let tx = Tx::from_bytes(&response.tx)?; - // We calculate the sender and the nonce for the transaction. - // We use `payer` of the fees as the sender of the transaction and search for its - // signature information to find the nonce. - // If `payer` is not specified, we use the account which signed the transaction first, as - // the sender. - let (sender, nonce) = tx - .auth_info - .fee - .payer - .as_ref() - .map(|payer| self.search_payer_in_signer_infos(&tx.auth_info.signer_infos, payer)) - .unwrap_or_else(|| { - let signer_info = tx.auth_info.signer_infos.get(0).ok_or_else(|| { - ChainCommunicationError::from_other_str("no signer info in default signer") - })?; - self.convert_signer_info_into_account_id_and_nonce(signer_info) - }) - .map(|(a, n)| CosmosAddress::from_account_id(a).map(|a| (a.digest(), n)))??; + let contract = Self::contract(&tx)?; + let (sender, nonce) = self.sender_and_nonce(&tx)?; // TODO support multiple denomination for amount let gas_limit = U256::from(tx.auth_info.fee.gas_limit);