feat: populate more fields in scraper for Cosmos (#4401)

### Description

Populate gas price, nonce and contract as a recipient of a transaction
(still naive handling).

### Backward compatibility

Yes

### Testing

E2E Test for Cosmos (no check in the invariant yet)

---------

Co-authored-by: Danil Nemirovsky <4614623+ameten@users.noreply.github.com>
pull/4412/head
Danil Nemirovsky 3 months ago committed by GitHub
parent 3c07ded5b7
commit 6f867a5974
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 41
      rust/chains/hyperlane-cosmos/src/libs/account.rs
  2. 25
      rust/chains/hyperlane-cosmos/src/libs/address.rs
  3. 2
      rust/chains/hyperlane-cosmos/src/libs/mod.rs
  4. 56
      rust/chains/hyperlane-cosmos/src/providers/mod.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<Self, Self::Error> {
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<CosmosAccountId<'_>> for H256 {
type Error = ChainCommunicationError;
/// Builds a H256 digest from a cosmos AccountId (Bech32 encoding)
fn try_from(account_id: CosmosAccountId) -> Result<Self, Self::Error> {
(&account_id).try_into()
}
}

@ -9,6 +9,7 @@ use hyperlane_core::{ChainCommunicationError, ChainResult, Error::Overflow, H256
use tendermint::account::Id as TendermintAccountId; use tendermint::account::Id as TendermintAccountId;
use tendermint::public_key::PublicKey as TendermintPublicKey; use tendermint::public_key::PublicKey as TendermintPublicKey;
use crate::libs::account::CosmosAccountId;
use crate::HyperlaneCosmosError; use crate::HyperlaneCosmosError;
/// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding /// 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()) let account_id = AccountId::new(prefix, tendermint_id.as_bytes())
.map_err(Into::<HyperlaneCosmosError>::into)?; .map_err(Into::<HyperlaneCosmosError>::into)?;
// Hex digest // 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)) Ok(CosmosAddress::new(account_id, digest))
} }
@ -69,14 +70,6 @@ impl CosmosAddress {
Ok(CosmosAddress::new(account_id, digest)) Ok(CosmosAddress::new(account_id, digest))
} }
/// Builds a H256 digest from a cosmos AccountId (Bech32 encoding)
fn bech32_decode(account_id: AccountId) -> ChainResult<H256> {
// 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 /// String representation of a cosmos AccountId
pub fn address(&self) -> String { pub fn address(&self) -> String {
self.account_id.to_string() self.account_id.to_string()
@ -92,17 +85,7 @@ impl TryFrom<&CosmosAddress> for H256 {
type Error = ChainCommunicationError; type Error = ChainCommunicationError;
fn try_from(cosmos_address: &CosmosAddress) -> Result<Self, Self::Error> { fn try_from(cosmos_address: &CosmosAddress) -> Result<Self, Self::Error> {
// `to_bytes()` decodes the Bech32 into a hex, represented as a byte vec CosmosAccountId::new(&cosmos_address.account_id).try_into()
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))
} }
} }
@ -111,7 +94,7 @@ impl FromStr for CosmosAddress {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let account_id = AccountId::from_str(s).map_err(Into::<HyperlaneCosmosError>::into)?; let account_id = AccountId::from_str(s).map_err(Into::<HyperlaneCosmosError>::into)?;
let digest = Self::bech32_decode(account_id.clone())?; let digest = CosmosAccountId::new(&account_id).try_into()?;
Ok(Self::new(account_id, digest)) Ok(Self::new(account_id, digest))
} }
} }

@ -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. /// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain.
pub mod address; pub mod address;

@ -1,5 +1,7 @@
use async_trait::async_trait; use async_trait::async_trait;
use cosmrs::cosmwasm::MsgExecuteContract;
use cosmrs::crypto::PublicKey; use cosmrs::crypto::PublicKey;
use cosmrs::tx::{MessageExt, SignerInfo};
use cosmrs::Tx; use cosmrs::Tx;
use tendermint::hash::Algorithm; use tendermint::hash::Algorithm;
use tendermint::Hash; use tendermint::Hash;
@ -13,6 +15,7 @@ use hyperlane_core::{
use crate::address::CosmosAddress; use crate::address::CosmosAddress;
use crate::grpc::WasmProvider; use crate::grpc::WasmProvider;
use crate::libs::account::CosmosAccountId;
use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer};
use self::grpc::WasmGrpcProvider; use self::grpc::WasmGrpcProvider;
@ -74,6 +77,18 @@ impl CosmosProvider {
pub fn rpc(&self) -> &HttpClient { pub fn rpc(&self) -> &HttpClient {
&self.rpc_client &self.rpc_client
} }
fn sender(&self, signer: &SignerInfo) -> Result<H256, ChainCommunicationError> {
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 { impl HyperlaneChain for CosmosProvider {
@ -145,34 +160,51 @@ impl HyperlaneProvider for CosmosProvider {
ChainCommunicationError::from_other_str("could not deserialize transaction") 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 let signer = tx
.auth_info .auth_info
.signer_infos .signer_infos
.get(0) .get(0)
.expect("there should be at least one signer"); .expect("there should be at least one signer");
let signer_public_key = signer let sender = self.sender(signer)?;
.public_key let nonce = signer.sequence;
.clone()
.ok_or_else(|| ChainCommunicationError::from_other_str("no public key"))?;
let public_key: PublicKey = signer_public_key.try_into()?; // TODO support multiple denomination for amount
let key = let gas_limit = U256::from(tx.auth_info.fee.gas_limit);
CosmosAddress::from_pubkey(public_key, &self.connection_conf.get_bech32_prefix())?; let fee = tx
let sender = key.digest(); .auth_info
.fee
.amount
.iter()
.fold(U256::zero(), |acc, a| acc + a.amount);
let gas_price = fee / gas_limit;
let tx_info = TxnInfo { let tx_info = TxnInfo {
hash: hash.to_owned(), hash: hash.to_owned(),
gas_limit: U256::from(response.tx_result.gas_wanted), gas_limit: U256::from(response.tx_result.gas_wanted),
max_priority_fee_per_gas: None, max_priority_fee_per_gas: None,
max_fee_per_gas: None, max_fee_per_gas: None,
gas_price: None, gas_price: Some(gas_price),
nonce: 0, nonce,
sender, sender,
recipient: None, recipient: Some(contract),
receipt: Some(TxnReceiptInfo { receipt: Some(TxnReceiptInfo {
gas_used: U256::from(response.tx_result.gas_used), gas_used: U256::from(response.tx_result.gas_used),
cumulative_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),
}), }),
}; };

Loading…
Cancel
Save