@ -1,8 +1,10 @@
use std ::str ::FromStr ;
use async_trait ::async_trait ;
use cosmrs ::cosmwasm ::MsgExecuteContract ;
use cosmrs ::crypto ::PublicKey ;
use cosmrs ::tx ::{ MessageExt , SequenceNumber , SignerInfo } ;
use cosmrs ::{ AccountId , Any , Coin , Tx } ;
use cosmrs ::tx ::{ MessageExt , SequenceNumber , SignerInfo , SignerPublicKey } ;
use cosmrs ::{ proto , AccountId , Any , Coin , Tx } ;
use itertools ::Itertools ;
use once_cell ::sync ::Lazy ;
use tendermint ::hash ::Algorithm ;
@ -11,20 +13,25 @@ use tendermint_rpc::{client::CompatMode, Client, HttpClient};
use time ::OffsetDateTime ;
use tracing ::{ error , warn } ;
use crypto ::decompress_public_key ;
use hyperlane_core ::{
BlockInfo , ChainCommunicationError , ChainInfo , ChainResult , ContractLocator , HyperlaneChain ,
HyperlaneDomain , HyperlaneProvider , TxnInfo , TxnReceiptInfo , H256 , U256 ,
AccountAddressType , BlockInfo , ChainCommunicationError , ChainInfo , ChainResult ,
ContractLocator , HyperlaneChain , HyperlaneDomain , HyperlaneProvider , TxnInfo , TxnReceiptInfo ,
H256 , U256 ,
} ;
use crate ::address ::CosmosAddress ;
use crate ::grpc ::{ WasmGrpcProvider , WasmProvider } ;
use crate ::libs ::account ::CosmosAccountId ;
use crate ::providers ::rpc ::CosmosRpcClient ;
use crate ::{ ConnectionConf , CosmosAmount , HyperlaneCosmosError , Signer } ;
use crate ::{
ConnectionConf , CosmosAccountId , CosmosAddress , CosmosAmount , HyperlaneCosmosError , Signer ,
} ;
/// Exponent value for atto units (10^-18).
const ATTO_EXPONENT : u32 = 18 ;
/// Injective public key type URL for protobuf Any
const INJECTIVE_PUBLIC_KEY_TYPE_URL : & str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey" ;
/// Abstraction over a connection to a Cosmos chain
#[ derive(Debug, Clone) ]
pub struct CosmosProvider {
@ -93,16 +100,74 @@ impl CosmosProvider {
HyperlaneCosmosError ::PublicKeyError ( "no public key for default signer" . to_owned ( ) )
} ) ? ;
let public_key = PublicKey ::try_from ( signer_public_key ) ? ;
let ( key , account_address_type ) = self . normalize_public_key ( signer_public_key ) ? ;
let public_key = PublicKey ::try_from ( key ) ? ;
let account_id = CosmosAccountId ::account_id_from_pubkey (
public_key ,
& self . connection_conf . get_bech32_prefix ( ) ,
& account_address_type ,
) ? ;
Ok ( ( account_id , signer_info . sequence ) )
}
fn normalize_public_key (
& self ,
signer_public_key : SignerPublicKey ,
) -> ChainResult < ( SignerPublicKey , AccountAddressType ) > {
let public_key_and_account_address_type = match signer_public_key {
SignerPublicKey ::Single ( pk ) = > ( SignerPublicKey ::from ( pk ) , AccountAddressType ::Bitcoin ) ,
SignerPublicKey ::LegacyAminoMultisig ( pk ) = > {
( SignerPublicKey ::from ( pk ) , AccountAddressType ::Bitcoin )
}
SignerPublicKey ::Any ( pk ) = > {
if pk . type_url ! = PublicKey ::ED25519_TYPE_URL
& & pk . type_url ! = PublicKey ::SECP256K1_TYPE_URL
& & pk . type_url ! = INJECTIVE_PUBLIC_KEY_TYPE_URL
{
let msg = format! (
"can only normalize public keys with a known TYPE_URL: {}, {}, {}" ,
PublicKey ::ED25519_TYPE_URL ,
PublicKey ::SECP256K1_TYPE_URL ,
INJECTIVE_PUBLIC_KEY_TYPE_URL
) ;
warn ! ( pk . type_url , msg ) ;
Err ( HyperlaneCosmosError ::PublicKeyError ( msg . to_owned ( ) ) ) ?
}
let ( pub_key , account_address_type ) =
if pk . type_url = = INJECTIVE_PUBLIC_KEY_TYPE_URL {
let any = Any {
type_url : PublicKey ::SECP256K1_TYPE_URL . to_owned ( ) ,
value : pk . value ,
} ;
let proto = proto ::cosmos ::crypto ::secp256k1 ::PubKey ::from_any ( & any )
. map_err ( Into ::< HyperlaneCosmosError > ::into ) ? ;
let decompressed = decompress_public_key ( & proto . key )
. map_err ( | e | HyperlaneCosmosError ::PublicKeyError ( e . to_string ( ) ) ) ? ;
let tendermint = tendermint ::PublicKey ::from_raw_secp256k1 ( & decompressed )
. ok_or_else ( | | {
HyperlaneCosmosError ::PublicKeyError (
"cannot create tendermint public key" . to_owned ( ) ,
)
} ) ? ;
( PublicKey ::from ( tendermint ) , AccountAddressType ::Ethereum )
} else {
( PublicKey ::try_from ( pk ) ? , AccountAddressType ::Bitcoin )
} ;
( SignerPublicKey ::Single ( pub_key ) , account_address_type )
}
} ;
Ok ( public_key_and_account_address_type )
}
/// 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.
@ -146,14 +211,15 @@ impl CosmosProvider {
let contract_execution_messages_len = contract_execution_messages . len ( ) ;
if contract_execution_messages_len > 1 {
error ! (
? tx_hash ,
? contract_execution_messages ,
"transaction contains multiple contract execution messages, we are indexing the first entry only" ) ;
let msg = "transaction contains multiple contract execution messages, we are indexing the first entry only" ;
warn ! ( ? tx_hash , ? contract_execution_messages , msg ) ;
Err ( ChainCommunicationError ::CustomError ( msg . to_owned ( ) ) ) ?
}
let any = contract_execution_messages . first ( ) . ok_or_else ( | | {
ChainCommunicationError ::from_other_str ( "could not find contract execution message" )
let msg = "could not find contract execution message" ;
warn ! ( ? tx_hash , msg ) ;
ChainCommunicationError ::from_other_str ( msg )
} ) ? ;
let proto =
ProtoMsgExecuteContract ::from_any ( any ) . map_err ( Into ::< HyperlaneCosmosError > ::into ) ? ;
@ -166,7 +232,7 @@ impl CosmosProvider {
/// The only denomination we support at the moment is the one we express gas minimum price
/// in the configuration of a chain. If fees contain an entry in a different denomination,
/// we report it in the logs.
fn report_unsupported_denominations ( & self , tx : & Tx , tx_hash : & H256 ) {
fn report_unsupported_denominations ( & self , tx : & Tx , tx_hash : & H256 ) -> ChainResult < ( ) > {
let supported_denomination = self . connection_conf . get_minimum_gas_price ( ) . denom ;
let unsupported_denominations = tx
. auth_info
@ -178,12 +244,17 @@ impl CosmosProvider {
. fold ( "" . to_string ( ) , | acc , denom | acc + ", " + denom ) ;
if ! unsupported_denominations . is_empty ( ) {
error ! (
let msg = "transaction contains fees in unsupported denominations, manual intervention is required" ;
warn ! (
? tx_hash ,
? supported_denomination ,
? unsupported_denominations ,
"transaction contains fees in unsupported denominations, manual intervention is required" ) ;
msg ,
) ;
Err ( ChainCommunicationError ::CustomError ( msg . to_owned ( ) ) ) ?
}
Ok ( ( ) )
}
/// Converts fees to a common denomination if necessary.
@ -278,7 +349,7 @@ impl HyperlaneProvider for CosmosProvider {
let ( sender , nonce ) = self . sender_and_nonce ( & tx ) ? ;
// TODO support multiple denominations for amount
self . report_unsupported_denominations ( & tx , hash ) ;
self . report_unsupported_denominations ( & tx , hash ) ? ;
let gas_limit = U256 ::from ( tx . auth_info . fee . gas_limit ) ;
let fee = tx