diff --git a/cw-orch-daemon/Cargo.toml b/cw-orch-daemon/Cargo.toml index 94ed53e8b..0cffe3558 100644 --- a/cw-orch-daemon/Cargo.toml +++ b/cw-orch-daemon/Cargo.toml @@ -17,11 +17,14 @@ exclude = [".env"] all-features = true [features] -default = [] +default = ["grpc"] # enable node-backed tests (ensure Docker is running) # run with `cargo test --jobs 1 --features node-tests` node-tests = [] eth = ["dep:ethers-signers", "dep:ethers-core"] +rpc = ["cosmrs/rpc"] +grpc = ["cosmrs/grpc", "dep:tonic"] + [dependencies] # Default deps cw-orch-core = { workspace = true } @@ -44,14 +47,14 @@ bitcoin = { version = "0.30.0" } hex = { version = "0.4.3" } ripemd = { version = "0.1.3" } tokio = { workspace = true, features = ["full"] } -tonic = { workspace = true, features = ["tls", "tls-roots"] } +tonic = { workspace = true, features = ["tls", "tls-roots"], optional = true } reqwest = { version = "0.11.9" } base64 = { version = "0.22.1" } hkd32 = { version = "0.7.0", features = ["bip39", "mnemonic", "bech32"] } rand_core = { version = "0.6.4", default-features = false } ed25519-dalek = { version = "2", features = ["serde"] } eyre = { version = "0.6" } -cosmrs = { version = "0.15.0", features = ["dev", "cosmwasm", "grpc"] } +cosmrs = { version = "0.15.0", features = ["dev", "cosmwasm"] } chrono = { version = "0.4" } base16 = { version = "0.2.1" } ring = { version = "0.17.3" } diff --git a/cw-orch-daemon/src/channel.rs b/cw-orch-daemon/src/channel.rs deleted file mode 100644 index 5a3837ff0..000000000 --- a/cw-orch-daemon/src/channel.rs +++ /dev/null @@ -1,144 +0,0 @@ -use cosmrs::proto::cosmos::base::tendermint::v1beta1::{ - service_client::ServiceClient, GetNodeInfoRequest, -}; -use cw_orch_core::{environment::ChainInfoOwned, log::connectivity_target}; -use tonic::transport::{Channel, ClientTlsConfig}; - -use super::error::DaemonError; - -/// A helper for constructing a gRPC channel -pub struct GrpcChannel {} - -impl GrpcChannel { - /// Connect to any of the provided gRPC endpoints - pub async fn connect(grpc: &[String], chain_id: &str) -> Result { - if grpc.is_empty() { - return Err(DaemonError::GRPCListIsEmpty); - } - - let mut successful_connections = vec![]; - - for address in grpc.iter() { - log::debug!(target: &connectivity_target(), "Trying to connect to endpoint: {}", address); - - // get grpc endpoint - let endpoint = Channel::builder(address.clone().try_into().unwrap()); - - // try to connect to grpc endpoint - let maybe_client = ServiceClient::connect(endpoint.clone()).await; - - // connection succeeded - let mut client = if maybe_client.is_ok() { - maybe_client? - } else { - log::warn!( - "Cannot connect to gRPC endpoint: {}, {:?}", - address, - maybe_client.unwrap_err() - ); - - // try HTTPS approach - // https://github.com/hyperium/tonic/issues/363#issuecomment-638545965 - if !(address.contains("https") || address.contains("443")) { - continue; - }; - - log::debug!(target: &connectivity_target(), "Attempting to connect with TLS"); - - // re attempt to connect - let endpoint = endpoint.clone().tls_config(ClientTlsConfig::new())?; - let maybe_client = ServiceClient::connect(endpoint.clone()).await; - - // connection still fails - if maybe_client.is_err() { - log::warn!( - "Cannot connect to gRPC endpoint: {}, {:?}", - address, - maybe_client.unwrap_err() - ); - continue; - }; - - maybe_client? - }; - - // get client information for verification down below - let node_info = client - .get_node_info(GetNodeInfoRequest {}) - .await? - .into_inner(); - - // local juno does not return a proper ChainId with epoch format - // verify we are connected to the expected network - if node_info.default_node_info.as_ref().unwrap().network != chain_id { - log::error!( - "Network mismatch: connection:{} != config:{}", - node_info.default_node_info.as_ref().unwrap().network, - chain_id - ); - continue; - } - - // add endpoint to succesful connections - successful_connections.push(endpoint.connect().await?) - } - - // we could not get any succesful connections - if successful_connections.is_empty() { - return Err(DaemonError::CannotConnectGRPC); - } - - Ok(successful_connections.pop().unwrap()) - } - - /// Create a gRPC channel from the chain info - pub async fn from_chain_info(chain_info: &ChainInfoOwned) -> Result { - GrpcChannel::connect(&chain_info.grpc_urls, &chain_info.chain_id).await - } -} - -#[cfg(test)] -mod tests { - /* - This test asserts breaking issues around the GRPC connection - */ - - use crate::DaemonAsync; - use speculoos::prelude::*; - - #[tokio::test] - #[serial_test::serial] - async fn no_connection() { - let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; - let grpcs = &["https://127.0.0.1:99999"]; - chain.grpc_urls = grpcs; - - let build_res = DaemonAsync::builder(chain) - .deployment_id("v0.1.0") - .build_sender(()) - .await; - - asserting!("there is no GRPC connection") - .that(&build_res.err().unwrap().to_string()) - .is_equal_to(String::from( - "Can not connect to any grpc endpoint that was provided.", - )) - } - - #[tokio::test] - #[serial_test::serial] - async fn network_grpcs_list_is_empty() { - let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; - let grpcs = &[]; - chain.grpc_urls = grpcs; - - let build_res = DaemonAsync::builder(chain) - .deployment_id("v0.1.0") - .build_sender(()) - .await; - - asserting!("GRPC list is empty") - .that(&build_res.err().unwrap().to_string()) - .is_equal_to(String::from("The list of grpc endpoints is empty")) - } -} diff --git a/cw-orch-daemon/src/core.rs b/cw-orch-daemon/src/core.rs index 34b34f161..a9576e55c 100644 --- a/cw-orch-daemon/src/core.rs +++ b/cw-orch-daemon/src/core.rs @@ -1,5 +1,5 @@ use crate::{ - queriers::CosmWasm, + queriers::{CosmWasm, CosmWasmBase}, senders::{builder::SenderBuilder, query::QuerySender}, DaemonAsyncBuilder, DaemonState, }; @@ -144,20 +144,18 @@ impl DaemonAsyncBase { } /// Query a contract. - pub async fn query( + pub async fn query( &self, query_msg: &Q, contract_address: &Addr, - ) -> Result { - let mut client = cosmos_modules::cosmwasm::query_client::QueryClient::new(self.channel()); - let resp = client - .smart_contract_state(cosmos_modules::cosmwasm::QuerySmartContractStateRequest { - address: contract_address.to_string(), - query_data: serde_json::to_vec(&query_msg)?, - }) + ) -> Result { + let querier = CosmWasmBase::::new_async(self.channel()); + + let resp: Vec = querier + ._contract_state(contract_address, serde_json::to_vec(&query_msg)?) .await?; - Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?) + Ok(from_str(from_utf8(&resp).unwrap())?) } /// Wait for a given amount of blocks. diff --git a/cw-orch-daemon/src/daemon_helpers.rs b/cw-orch-daemon/src/daemon_helpers.rs new file mode 100644 index 000000000..7d5605453 --- /dev/null +++ b/cw-orch-daemon/src/daemon_helpers.rs @@ -0,0 +1,173 @@ +mod common; +#[cfg(feature = "node-tests")] +mod tests { + /* + DaemonAsync contract general tests + */ + + use cw_orch_core::{contract::interface_traits::*, environment::TxHandler}; + use cw_orch_daemon::{ConditionalMigrate, ConditionalUpload, Daemon}; + use mock_contract::{InstantiateMsg, MigrateMsg, QueryMsg}; + + use cosmwasm_std::Addr; + + use speculoos::prelude::*; + + use crate::common::Id; + + #[test] + #[serial_test::serial] + fn helper_traits() { + use cw_orch_networks::networks; + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let daemon = Daemon::builder() + .chain(networks::LOCAL_JUNO) + .handle(runtime.handle()) + .build() + .unwrap(); + + let sender = daemon.sender(); + + let contract = mock_contract::MockContract::new( + format!("test:mock_contract:{}", Id::new()), + daemon.clone(), + ); + + asserting!("address is not present") + .that(&contract.address()) + .is_err(); + + asserting!("upload_if_needed is ok") + .that(&contract.upload_if_needed()) + .is_ok(); + + asserting!("latest_is_uploaded is true") + .that(&contract.latest_is_uploaded().unwrap()) + .is_true(); + + let init_msg = &InstantiateMsg {}; + + let _ = contract.instantiate(init_msg, Some(&Addr::unchecked(sender)), Some(&[])); + + asserting!("address is present") + .that(&contract.address()) + .is_ok(); + + asserting!("migrate_if_needed is none") + .that( + &contract + .migrate_if_needed(&MigrateMsg { + t: "success".to_string(), + }) + .unwrap(), + ) + .is_none(); + + asserting!("is_running_latest is true") + .that(&contract.is_running_latest().unwrap()) + .is_true(); + + let _ = contract.upload(); + + asserting!("is_running_latest is false") + .that(&contract.is_running_latest().unwrap()) + .is_false(); + + asserting!("migrate_if_needed is some") + .that( + &contract + .migrate_if_needed(&MigrateMsg { + t: "success".to_string(), + }) + .unwrap(), + ) + .is_some(); + + asserting!("code_id is ok") + .that(&contract.code_id()) + .is_ok(); + } + + // #[test] + // #[serial_test::serial] + // fn wrong_min_fee() { + // use cw_orch::prelude::networks; + + // let runtime = tokio::runtime::Runtime::new().unwrap(); + + // let mut chain = networks::UNI_6; + // chain.gas_price = 0.00001; + + // let daemon = Daemon::builder() + // .chain(chain) + // .handle(runtime.handle()) + // .mnemonic("tide genuine angle mass fall promote blind skull swim army maximum add peasant fringe uncle october female crisp voyage blind extend jeans give wrap") + // .build() + // .unwrap(); + + // let contract = mock_contract::MockContract::new( + // format!("test:mock_contract:{}", Id::new()), + // daemon.clone(), + // ); + + // contract.upload().unwrap(); + // } + + #[test] + #[serial_test::serial] + fn cw_orch_interface_traits() { + use cw_orch_networks::networks; + + let runtime = tokio::runtime::Runtime::new().unwrap(); + + let daemon = Daemon::builder() + .chain(networks::LOCAL_JUNO) + .handle(runtime.handle()) + .build() + .unwrap(); + + let sender = daemon.sender(); + + let contract = mock_contract::MockContract::new( + format!("test:mock_contract:{}", Id::new()), + daemon.clone(), + ); + + // upload contract + let upload_res = contract.upload(); + asserting!("upload is successful").that(&upload_res).is_ok(); + + let code_id = upload_res.unwrap().logs[0].events[1].attributes[1] + .value + .clone(); + + log::info!("Using code_id {}", code_id); + + // instantiate contract on chain + let init_res = contract.instantiate(&InstantiateMsg {}, Some(&sender), None); + asserting!("instantiate is successful") + .that(&init_res) + .is_ok(); + + // do a query and validate its successful + let query_res = contract.query::(&QueryMsg::FirstQuery {}); + asserting!("query is successful").that(&query_res).is_ok(); + + // validate migrations are successful + let migrate_res = contract.migrate( + &MigrateMsg { + t: "success".to_string(), + }, + code_id.parse::().unwrap(), + ); + asserting!("migrate is successful") + .that(&migrate_res) + .is_ok(); + + asserting!("that upload_if_needed returns None") + .that(&contract.upload_if_needed().unwrap()) + .is_none(); + } +} diff --git a/cw-orch-daemon/src/error.rs b/cw-orch-daemon/src/error.rs index 51e400a39..c66871b29 100644 --- a/cw-orch-daemon/src/error.rs +++ b/cw-orch-daemon/src/error.rs @@ -24,13 +24,22 @@ pub enum DaemonError { VarError(#[from] ::std::env::VarError), #[error(transparent)] AnyError(#[from] ::anyhow::Error), + + #[cfg(feature = "grpc")] #[error(transparent)] Status(#[from] ::tonic::Status), + + #[cfg(feature = "grpc")] #[error(transparent)] TransportError(#[from] ::tonic::transport::Error), + #[error(transparent)] TendermintError(#[from] ::cosmrs::tendermint::Error), #[error(transparent)] + TendermintRPCError(#[from] cosmrs::rpc::Error), + #[error(transparent)] + ProseEncoreError(#[from] prost::EncodeError), + #[error(transparent)] CwEnvError(#[from] ::cw_orch_core::CwEnvError), #[error(transparent)] StripPrefixPath(#[from] std::path::StripPrefixError), @@ -102,6 +111,8 @@ pub enum DaemonError { NewNetwork(String), #[error("Can not connect to any grpc endpoint that was provided.")] CannotConnectGRPC, + #[error("Can not connect to any rpc endpoint that was provided.")] + CannotConnectRPC, #[error("tx failed: {reason} with code {code}")] TxFailed { code: usize, reason: String }, #[error("The list of grpc endpoints is empty")] diff --git a/cw-orch-daemon/src/lib.rs b/cw-orch-daemon/src/lib.rs index 4f5bffcb3..9b05dae9b 100644 --- a/cw-orch-daemon/src/lib.rs +++ b/cw-orch-daemon/src/lib.rs @@ -10,13 +10,13 @@ pub mod proto; pub mod env; pub mod keys; pub mod live_mock; + pub mod queriers; pub mod senders; pub mod tx_broadcaster; pub mod tx_builder; mod builder; -mod channel; mod core; mod error; mod log; @@ -24,7 +24,7 @@ mod state; mod sync; mod tx_resp; -pub use self::{builder::*, channel::*, core::*, error::*, state::*, sync::*, tx_resp::*}; +pub use self::{builder::*, core::*, error::*, state::*, sync::*, tx_resp::*}; pub use cw_orch_networks::networks; pub use senders::{query::QuerySender, tx::TxSender, CosmosOptions, Wallet}; pub use tx_builder::TxBuilder; diff --git a/cw-orch-daemon/src/live_mock.rs b/cw-orch-daemon/src/live_mock.rs index a3664c914..f424541b3 100644 --- a/cw-orch-daemon/src/live_mock.rs +++ b/cw-orch-daemon/src/live_mock.rs @@ -1,9 +1,8 @@ //! Live mock is a mock that uses a live chain to query for data. //! It can be used to do chain-backed unit-testing. It can't be used for state-changing operations. -use crate::queriers::Bank; -use crate::queriers::CosmWasm; -use crate::queriers::Staking; +use crate::create_transport_channel; +use crate::queriers::{Bank, CosmWasm, DaemonQuerier, Staking}; use crate::RUNTIME; use cosmwasm_std::testing::{MockApi, MockStorage}; use cosmwasm_std::Addr; @@ -14,6 +13,8 @@ use cosmwasm_std::Binary; use cosmwasm_std::Delegation; use cosmwasm_std::Empty; use cosmwasm_std::StakingQuery; +use tokio::runtime::Runtime; + use cosmwasm_std::{ from_json, to_json_binary, Coin, ContractResult, OwnedDeps, Querier, QuerierResult, QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, @@ -26,8 +27,6 @@ use std::marker::PhantomData; use std::str::FromStr; use tonic::transport::Channel; -use crate::channel::GrpcChannel; - fn to_cosmwasm_coin(c: cosmrs::proto::cosmos::base::v1beta1::Coin) -> Coin { Coin { amount: Uint128::from_str(&c.amount).unwrap(), @@ -55,7 +54,10 @@ pub fn mock_dependencies( /// Querier struct that fetches queries on-chain directly pub struct WasmMockQuerier { - channel: Channel, + #[cfg(feature = "grpc")] + channel: tonic::transport::Channel, + #[cfg(feature = "rpc")] + channel: cosmrs::rpc::HttpClient, } impl Querier for WasmMockQuerier { @@ -187,7 +189,7 @@ impl WasmMockQuerier { /// Creates a querier from chain information pub fn new(chain: ChainInfoOwned) -> Self { let channel = RUNTIME - .block_on(GrpcChannel::connect( + .block_on(CosmosClient::connect( &chain.grpc_urls, chain.chain_id.as_str(), )) diff --git a/cw-orch-daemon/src/proto/injective.rs b/cw-orch-daemon/src/proto/injective.rs index 1cf99348b..b2dd1e2bf 100644 --- a/cw-orch-daemon/src/proto/injective.rs +++ b/cw-orch-daemon/src/proto/injective.rs @@ -3,7 +3,9 @@ use crate::DaemonError; use cosmrs::tx::Raw; use cosmrs::tx::SignDoc; -use prost::Name; +use cosmrs::{ + proto::traits::{Message, Name}, +}; #[cfg(feature = "eth")] use crate::keys::private::PrivateKey; @@ -30,11 +32,6 @@ pub struct InjectivePubKey { impl Name for InjectivePubKey { const NAME: &'static str = "PubKey"; const PACKAGE: &'static str = "/injective.crypto.v1beta1.ethsecp256k1"; - - /// Workaround until tokio-rs/prost#923 is released - fn full_name() -> String { - format!("{}.{}", Self::PACKAGE, Self::NAME) - } } pub trait InjectiveSigner { diff --git a/cw-orch-daemon/src/queriers.rs b/cw-orch-daemon/src/queriers.rs index 627f591c1..02c05195b 100644 --- a/cw-orch-daemon/src/queriers.rs +++ b/cw-orch-daemon/src/queriers.rs @@ -21,44 +21,44 @@ //! # }) //! ``` -/// macro for constructing and performing a query on a CosmosSDK module. -#[macro_export] -macro_rules! cosmos_query { - ($self:ident, $module:ident, $func_name:ident, $request_type:ident { $($field:ident : $value:expr),* $(,)? }) => { - { - use $crate::cosmos_modules::$module::{ - query_client::QueryClient, $request_type, - }; - let mut client = QueryClient::new($self.channel.clone()); - #[allow(clippy::redundant_field_names)] - let request = $request_type { $($field : $value),* }; - let response = client.$func_name(request.clone()).await?.into_inner(); - ::log::trace!( - "cosmos_query: {:?} resulted in: {:?}", - request, - response - ); - response - } -}; +pub const MAX_TX_QUERY_RETRIES: usize = 50; + +#[cfg(feature = "rpc")] +pub mod rpc; +#[cfg(feature = "rpc")] +pub use rpc::*; +#[cfg(feature = "grpc")] +pub mod grpc; +#[cfg(feature = "grpc")] +pub use grpc::*; + +pub mod clients; + +/// Constructor for a querier over a given channel +pub trait DaemonQuerier { + /// Construct an new querier over a given channel + #[cfg(feature = "rpc")] + fn new(client: cosmrs::rpc::HttpClient) -> Self; + #[cfg(feature = "grpc")] + fn new(channel: tonic::transport::Channel) -> Self; } mod authz; -mod bank; -mod cosmwasm; mod env; -mod feegrant; -mod gov; -mod ibc; -mod node; -mod staking; +// mod bank; +// mod cosmwasm; +// mod feegrant; +// mod gov; +// mod ibc; +// mod node; +// mod staking; pub use authz::Authz; -pub use bank::{cosmrs_to_cosmwasm_coins, Bank}; -pub use cosmwasm::{CosmWasm, CosmWasmBase}; -pub use feegrant::FeeGrant; -pub use ibc::Ibc; -pub use node::Node; +// pub use bank::{cosmrs_to_cosmwasm_coins, Bank}; +// pub use cosmwasm::{CosmWasm, CosmWasmBase}; +// pub use feegrant::FeeGrant; +// pub use ibc::Ibc; +// pub use node::Node; // this two containt structs that are helpers for the queries pub use gov::*; diff --git a/cw-orch-daemon/src/queriers/clients/auth.rs b/cw-orch-daemon/src/queriers/clients/auth.rs new file mode 100644 index 000000000..e9915a204 --- /dev/null +++ b/cw-orch-daemon/src/queriers/clients/auth.rs @@ -0,0 +1,32 @@ +// Only a simple implementation to not overload the tx builder +use tonic::transport::Channel; + +use crate::{cosmos_query, queriers::DaemonQuerier, DaemonError}; + +/// Queries for Cosmos Bank Module +pub struct Auth { + channel: Channel, +} + +impl DaemonQuerier for Auth { + fn new(channel: Channel) -> Self { + Self { channel } + } +} + +impl Auth { + /// Query spendable balance for address + pub async fn account(&self, address: impl Into) -> Result, DaemonError> { + + + let resp = cosmos_query!( + self, + auth, + account, + QueryAccountRequest { + address: address.into() + } + ); + Ok(resp.account.unwrap().value) + } +} diff --git a/cw-orch-daemon/src/queriers/bank.rs b/cw-orch-daemon/src/queriers/clients/bank.rs similarity index 93% rename from cw-orch-daemon/src/queriers/bank.rs rename to cw-orch-daemon/src/queriers/clients/bank.rs index d5b765184..9bc53e0bc 100644 --- a/cw-orch-daemon/src/queriers/bank.rs +++ b/cw-orch-daemon/src/queriers/clients/bank.rs @@ -45,15 +45,17 @@ impl Bank { address: impl Into, denom: Option, ) -> Result, DaemonError> { - use cosmos_modules::bank::query_client::QueryClient; match denom { Some(denom) => { - let mut client: QueryClient = QueryClient::new(self.channel.clone()); - let request = cosmos_modules::bank::QueryBalanceRequest { - address: address.into(), - denom, - }; - let resp = client.balance(request).await?.into_inner(); + let resp = cosmos_query!( + self, + bank, + balance, + QueryBalanceRequest { + address: address.into(), + denom: denom, + } + ); let coin = resp.balance.unwrap(); Ok(vec![cosmrs_to_cosmwasm_coin(coin)?]) } diff --git a/cw-orch-daemon/src/queriers/cosmwasm.rs b/cw-orch-daemon/src/queriers/clients/cosmwasm.rs similarity index 100% rename from cw-orch-daemon/src/queriers/cosmwasm.rs rename to cw-orch-daemon/src/queriers/clients/cosmwasm.rs diff --git a/cw-orch-daemon/src/queriers/feegrant.rs b/cw-orch-daemon/src/queriers/clients/feegrant.rs similarity index 100% rename from cw-orch-daemon/src/queriers/feegrant.rs rename to cw-orch-daemon/src/queriers/clients/feegrant.rs diff --git a/cw-orch-daemon/src/queriers/gov.rs b/cw-orch-daemon/src/queriers/clients/gov.rs similarity index 100% rename from cw-orch-daemon/src/queriers/gov.rs rename to cw-orch-daemon/src/queriers/clients/gov.rs diff --git a/cw-orch-daemon/src/queriers/ibc.rs b/cw-orch-daemon/src/queriers/clients/ibc.rs similarity index 100% rename from cw-orch-daemon/src/queriers/ibc.rs rename to cw-orch-daemon/src/queriers/clients/ibc.rs diff --git a/cw-orch-daemon/src/queriers/clients/mod.rs b/cw-orch-daemon/src/queriers/clients/mod.rs new file mode 100644 index 000000000..85b5586e8 --- /dev/null +++ b/cw-orch-daemon/src/queriers/clients/mod.rs @@ -0,0 +1,45 @@ +//! # DaemonQuerier +//! +//! DaemonAsync queriers are gRPC query clients for the CosmosSDK modules. They can be used to query the different modules (Bank, Ibc, Authz, ...). +//! +//! ## Usage +//! +//! You will need to acquire a [gRPC channel](Channel) to a running CosmosSDK node to be able to use the queriers. +//! Here is an example of how to acquire one using the DaemonAsync builder. +//! +//! ```no_run +//! // require the querier you want to use, in this case Node +//! use cw_orch_daemon::{queriers::Node, DaemonAsync, networks, queriers::DaemonQuerier}; +//! # tokio_test::block_on(async { +//! // call the builder and configure it as you need +//! let daemon = DaemonAsync::builder() +//! .chain(networks::LOCAL_JUNO) +//! .build() +//! .await.unwrap(); +//! // now you can use the Node querier: +//! let node = Node::new(daemon.channel()); +//! let node_info = node.info(); +//! # }) +//! ``` + +mod auth; +mod bank; +mod cosmwasm; +mod feegrant; +mod gov; +mod ibc; +mod node; +mod staking; +mod tx; + +pub use auth::Auth; +pub use bank::Bank; +pub use cosmwasm::{CosmWasm, CosmWasmBase}; +pub use feegrant::Feegrant; +pub use ibc::Ibc; +pub use node::Node; +pub use tx::Tx; + +// this two containt structs that are helpers for the queries +pub use gov::*; +pub use staking::*; diff --git a/cw-orch-daemon/src/queriers/node.rs b/cw-orch-daemon/src/queriers/clients/node.rs similarity index 100% rename from cw-orch-daemon/src/queriers/node.rs rename to cw-orch-daemon/src/queriers/clients/node.rs diff --git a/cw-orch-daemon/src/queriers/staking.rs b/cw-orch-daemon/src/queriers/clients/staking.rs similarity index 100% rename from cw-orch-daemon/src/queriers/staking.rs rename to cw-orch-daemon/src/queriers/clients/staking.rs diff --git a/cw-orch-daemon/src/queriers/clients/tx.rs b/cw-orch-daemon/src/queriers/clients/tx.rs new file mode 100644 index 000000000..9853a16b3 --- /dev/null +++ b/cw-orch-daemon/src/queriers/clients/tx.rs @@ -0,0 +1,35 @@ +// Only a simple implementation to not overload the tx builder + +use cosmrs::{proto::cosmos::base::abci::v1beta1::TxResponse, tx::Raw}; +use tonic::transport::Channel; + +use crate::{cosmos_modules, queriers::DaemonQuerier, DaemonError}; + +/// Queries for Cosmos Bank Module +pub struct Tx { + channel: Channel, +} + +impl DaemonQuerier for Tx { + fn new(channel: Channel) -> Self { + Self { channel } + } +} + +impl Tx { + /// Query spendable balance for address + pub async fn broadcast(&self, tx: Raw) -> Result { + let mut client = + cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { + tx_bytes: tx.to_bytes()?, + mode: cosmos_modules::tx::BroadcastMode::Sync.into(), + }) + .await? + .into_inner(); + + Ok(resp.tx_response.unwrap()) + } +} diff --git a/cw-orch-daemon/src/queriers/grpc.rs b/cw-orch-daemon/src/queriers/grpc.rs new file mode 100644 index 000000000..dc420403e --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc.rs @@ -0,0 +1,21 @@ +/// macro for constructing and performing a query on a CosmosSDK module. +#[macro_export] +macro_rules! cosmos_query { + ($self:ident, $module:ident, $func_name:ident, $request_type:ident { $($field:ident : $value:expr),* $(,)? }) => { + { + use $crate::cosmos_modules::$module::{ + query_client::QueryClient, $request_type, + }; + let mut client = QueryClient::new($self.channel.clone()); + #[allow(clippy::redundant_field_names)] + let request = $request_type { $($field : $value),* }; + let response = client.$func_name(request.clone()).await?.into_inner(); + ::log::trace!( + "cosmos_query: {:?} resulted in: {:?}", + request, + response + ); + response + } +}; +} \ No newline at end of file diff --git a/cw-orch-daemon/src/queriers/grpc_old/auth.rs b/cw-orch-daemon/src/queriers/grpc_old/auth.rs new file mode 100644 index 000000000..25f33d70b --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/auth.rs @@ -0,0 +1,30 @@ +// Only a simple implementation to not overload the tx builder +use tonic::transport::Channel; + +use crate::{cosmos_query, queriers::DaemonQuerier, DaemonError}; + +/// Queries for Cosmos Bank Module +pub struct Auth { + channel: Channel, +} + +impl DaemonQuerier for Auth { + fn new(channel: Channel) -> Self { + Self { channel } + } +} + +impl Auth { + /// Query spendable balance for address + pub async fn account(&self, address: impl Into) -> Result, DaemonError> { + let resp = cosmos_query!( + self, + auth, + account, + QueryAccountRequest { + address: address.into() + } + ); + Ok(resp.account.unwrap().value) + } +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/bank.rs b/cw-orch-daemon/src/queriers/grpc_old/bank.rs new file mode 100644 index 000000000..9bc53e0bc --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/bank.rs @@ -0,0 +1,196 @@ +use crate::{cosmos_modules, error::DaemonError, senders::query::QuerySender, DaemonBase}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cosmwasm_std::{Coin, StdError}; +use cw_orch_core::environment::{BankQuerier, Querier, QuerierGetter}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Queries for Cosmos Bank Module +/// All the async function are prefixed with `_` +pub struct Bank { + pub channel: Channel, + pub rt_handle: Option, +} + +impl Bank { + pub fn new(daemon: &DaemonBase) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl Querier for Bank { + type Error = DaemonError; +} + +impl QuerierGetter for DaemonBase { + fn querier(&self) -> Bank { + Bank::new(self) + } +} + +impl Bank { + /// Query the bank balance of a given address + /// If denom is None, returns all balances + pub async fn _balance( + &self, + address: impl Into, + denom: Option, + ) -> Result, DaemonError> { + match denom { + Some(denom) => { + let resp = cosmos_query!( + self, + bank, + balance, + QueryBalanceRequest { + address: address.into(), + denom: denom, + } + ); + let coin = resp.balance.unwrap(); + Ok(vec![cosmrs_to_cosmwasm_coin(coin)?]) + } + None => { + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = cosmos_modules::bank::QueryAllBalancesRequest { + address: address.into(), + ..Default::default() + }; + let resp = client.all_balances(request).await?.into_inner(); + Ok(cosmrs_to_cosmwasm_coins(resp.balances)?) + } + } + } + + /// Query spendable balance for address + pub async fn _spendable_balances( + &self, + address: impl Into, + ) -> Result, DaemonError> { + let spendable_balances: cosmos_modules::bank::QuerySpendableBalancesResponse = cosmos_query!( + self, + bank, + spendable_balances, + QuerySpendableBalancesRequest { + address: address.into(), + pagination: None, + } + ); + Ok(cosmrs_to_cosmwasm_coins(spendable_balances.balances)?) + } + + /// Query total supply in the bank + pub async fn _total_supply(&self) -> Result, DaemonError> { + let total_supply: cosmos_modules::bank::QueryTotalSupplyResponse = cosmos_query!( + self, + bank, + total_supply, + QueryTotalSupplyRequest { pagination: None } + ); + Ok(cosmrs_to_cosmwasm_coins(total_supply.supply)?) + } + + /// Query total supply in the bank for a denom + pub async fn _supply_of(&self, denom: impl Into) -> Result { + let supply_of: cosmos_modules::bank::QuerySupplyOfResponse = cosmos_query!( + self, + bank, + supply_of, + QuerySupplyOfRequest { + denom: denom.into() + } + ); + Ok(cosmrs_to_cosmwasm_coin(supply_of.amount.unwrap())?) + } + + /// Query params + pub async fn _params(&self) -> Result { + let params: cosmos_modules::bank::QueryParamsResponse = + cosmos_query!(self, bank, params, QueryParamsRequest {}); + Ok(params.params.unwrap()) + } + + /// Query denom metadata + pub async fn _denom_metadata( + &self, + denom: impl Into, + ) -> Result { + let denom_metadata: cosmos_modules::bank::QueryDenomMetadataResponse = cosmos_query!( + self, + bank, + denom_metadata, + QueryDenomMetadataRequest { + denom: denom.into() + } + ); + Ok(denom_metadata.metadata.unwrap()) + } + + /// Query denoms metadata with pagination + /// + /// see [PageRequest] for pagination + pub async fn _denoms_metadata( + &self, + pagination: Option, + ) -> Result, DaemonError> { + let denoms_metadata: cosmos_modules::bank::QueryDenomsMetadataResponse = cosmos_query!( + self, + bank, + denoms_metadata, + QueryDenomsMetadataRequest { + pagination: pagination + } + ); + Ok(denoms_metadata.metadatas) + } +} + +pub fn cosmrs_to_cosmwasm_coin( + c: cosmrs::proto::cosmos::base::v1beta1::Coin, +) -> Result { + Ok(Coin { + amount: c.amount.parse()?, + denom: c.denom, + }) +} + +pub fn cosmrs_to_cosmwasm_coins( + c: Vec, +) -> Result, StdError> { + c.into_iter().map(cosmrs_to_cosmwasm_coin).collect() +} +impl BankQuerier for Bank { + fn balance( + &self, + address: impl Into, + denom: Option, + ) -> Result, Self::Error> { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._balance(address, denom)) + } + + fn total_supply(&self) -> Result, Self::Error> { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._total_supply()) + } + + fn supply_of(&self, denom: impl Into) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._supply_of(denom)) + } +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/cosmwasm.rs b/cw-orch-daemon/src/queriers/grpc_old/cosmwasm.rs new file mode 100644 index 000000000..5dd15329b --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/cosmwasm.rs @@ -0,0 +1,325 @@ +use std::{marker::PhantomData, str::FromStr}; + +use crate::senders::query::QuerySender; +use crate::senders::QueryOnlySender; +use crate::{cosmos_modules, error::DaemonError, DaemonBase}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cosmrs::AccountId; +use cosmwasm_std::{ + from_json, instantiate2_address, to_json_binary, CanonicalAddr, CodeInfoResponse, + ContractInfoResponse, HexBinary, +}; +use cw_orch_core::environment::Environment; +use cw_orch_core::{ + contract::interface_traits::Uploadable, + environment::{Querier, QuerierGetter, WasmQuerier}, +}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Querier for the CosmWasm SDK module +/// All the async function are prefixed with `_` +pub struct CosmWasmBase { + pub channel: Channel, + pub rt_handle: Option, + _sender: PhantomData, +} + +pub type CosmWasm = CosmWasmBase; + +impl CosmWasmBase { + pub fn new(daemon: &DaemonBase) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + _sender: PhantomData, + } + } + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + _sender: PhantomData, + } + } + pub fn new_sync(channel: Channel, handle: &Handle) -> Self { + Self { + channel, + rt_handle: Some(handle.clone()), + _sender: PhantomData, + } + } +} + +impl QuerierGetter> for DaemonBase { + fn querier(&self) -> CosmWasmBase { + CosmWasmBase::new(self) + } +} + +impl Querier for CosmWasmBase { + type Error = DaemonError; +} + +impl CosmWasmBase { + /// Query code_id by hash + pub async fn _code_id_hash(&self, code_id: u64) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryCodeRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryCodeRequest { code_id }; + let resp = client.code(request).await?.into_inner(); + let contract_hash = resp.code_info.unwrap().data_hash; + Ok(contract_hash.into()) + } + + /// Query contract info + pub async fn _contract_info( + &self, + address: impl Into, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryContractInfoRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryContractInfoRequest { + address: address.into(), + }; + let resp = client.contract_info(request).await?.into_inner(); + let contract_info = resp.contract_info.unwrap(); + + let mut c = ContractInfoResponse::default(); + c.code_id = contract_info.code_id; + c.creator = contract_info.creator; + c.admin = if contract_info.admin.is_empty() { + None + } else { + Some(contract_info.admin) + }; + c.ibc_port = if contract_info.ibc_port_id.is_empty() { + None + } else { + Some(contract_info.ibc_port_id) + }; + Ok(c) + } + + /// Query contract history + pub async fn _contract_history( + &self, + address: impl Into, + pagination: Option, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryContractHistoryRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryContractHistoryRequest { + address: address.into(), + pagination, + }; + Ok(client.contract_history(request).await?.into_inner()) + } + + /// Query contract state + pub async fn _contract_state( + &self, + address: impl Into, + query_data: Vec, + ) -> Result, DaemonError> { + use cosmos_modules::cosmwasm::{query_client::*, QuerySmartContractStateRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QuerySmartContractStateRequest { + address: address.into(), + query_data, + }; + Ok(client + .smart_contract_state(request) + .await? + .into_inner() + .data) + } + + /// Query all contract state + pub async fn _all_contract_state( + &self, + address: impl Into, + pagination: Option, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryAllContractStateRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryAllContractStateRequest { + address: address.into(), + pagination, + }; + Ok(client.all_contract_state(request).await?.into_inner()) + } + + /// Query code + pub async fn _code(&self, code_id: u64) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryCodeRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryCodeRequest { code_id }; + let response = client.code(request).await?.into_inner().code_info.unwrap(); + + Ok(cosmrs_to_cosmwasm_code_info(response)) + } + + /// Query code bytes + pub async fn _code_data(&self, code_id: u64) -> Result, DaemonError> { + use cosmos_modules::cosmwasm::{query_client::*, QueryCodeRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryCodeRequest { code_id }; + Ok(client.code(request).await?.into_inner().data) + } + + /// Query codes + pub async fn _codes( + &self, + pagination: Option, + ) -> Result, DaemonError> { + use cosmos_modules::cosmwasm::{query_client::*, QueryCodesRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryCodesRequest { pagination }; + let response = client.codes(request).await?.into_inner().code_infos; + + Ok(response + .into_iter() + .map(cosmrs_to_cosmwasm_code_info) + .collect()) + } + + /// Query pinned codes + pub async fn _pinned_codes( + &self, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryPinnedCodesRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryPinnedCodesRequest { pagination: None }; + Ok(client.pinned_codes(request).await?.into_inner()) + } + + /// Query contracts by code + pub async fn _contract_by_codes( + &self, + code_id: u64, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryContractsByCodeRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryContractsByCodeRequest { + code_id, + pagination: None, + }; + Ok(client.contracts_by_code(request).await?.into_inner()) + } + + /// Query raw contract state + pub async fn _contract_raw_state( + &self, + address: impl Into, + query_data: Vec, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryRawContractStateRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let request = QueryRawContractStateRequest { + address: address.into(), + query_data, + }; + Ok(client.raw_contract_state(request).await?.into_inner()) + } + + /// Query params + pub async fn _params( + &self, + ) -> Result { + use cosmos_modules::cosmwasm::{query_client::*, QueryParamsRequest}; + let mut client: QueryClient = QueryClient::new(self.channel.clone()); + Ok(client.params(QueryParamsRequest {}).await?.into_inner()) + } +} + +impl WasmQuerier for CosmWasmBase { + type Chain = DaemonBase; + fn code_id_hash(&self, code_id: u64) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._code_id_hash(code_id)) + } + + fn contract_info( + &self, + address: impl Into, + ) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._contract_info(address)) + } + + fn raw_query( + &self, + address: impl Into, + query_data: Vec, + ) -> Result, Self::Error> { + let response = self + .rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._contract_raw_state(address, query_data))?; + + Ok(response.data) + } + + fn smart_query( + &self, + address: impl Into, + query_data: &Q, + ) -> Result { + let response = self + .rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._contract_state(address, to_json_binary(&query_data)?.to_vec()))?; + + Ok(from_json(response)?) + } + + fn code(&self, code_id: u64) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._code(code_id)) + } + + fn instantiate2_addr( + &self, + code_id: u64, + creator: impl Into, + salt: cosmwasm_std::Binary, + ) -> Result { + let creator_str = creator.into(); + let account_id = AccountId::from_str(&creator_str)?; + let prefix = account_id.prefix(); + let canon = account_id.to_bytes(); + let checksum = self.code_id_hash(code_id)?; + let addr = instantiate2_address(checksum.as_slice(), &CanonicalAddr(canon.into()), &salt)?; + + Ok(AccountId::new(prefix, &addr.0)?.to_string()) + } + + fn local_hash< + T: cw_orch_core::contract::interface_traits::Uploadable + + cw_orch_core::contract::interface_traits::ContractInstance>, + >( + &self, + contract: &T, + ) -> Result { + ::wasm(contract.environment().daemon.chain_info()).checksum() + } +} + +pub fn cosmrs_to_cosmwasm_code_info( + code_info: cosmrs::proto::cosmwasm::wasm::v1::CodeInfoResponse, +) -> CodeInfoResponse { + let mut c = CodeInfoResponse::default(); + c.code_id = code_info.code_id; + c.creator = code_info.creator; + c.checksum = code_info.data_hash.into(); + c +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/feegrant.rs b/cw-orch-daemon/src/queriers/grpc_old/feegrant.rs new file mode 100644 index 000000000..5937075d4 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/feegrant.rs @@ -0,0 +1,78 @@ +use crate::{cosmos_modules, error::DaemonError, Daemon}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cw_orch_core::environment::{Querier, QuerierGetter}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Querier for the Cosmos Gov module +/// All the async function are prefixed with `_` +pub struct FeeGrant { + pub channel: Channel, + pub rt_handle: Option, +} + +impl FeeGrant { + pub fn new(daemon: &Daemon) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl Querier for FeeGrant { + type Error = DaemonError; +} + +impl QuerierGetter for Daemon { + fn querier(&self) -> FeeGrant { + FeeGrant::new(self) + } +} + +impl FeeGrant { + /// Query all allowances granted to the grantee address by a granter address + pub async fn _allowance( + &self, + granter: impl Into, + grantee: impl Into, + ) -> Result { + let allowance: cosmos_modules::feegrant::QueryAllowanceResponse = cosmos_query!( + self, + feegrant, + allowance, + QueryAllowanceRequest { + granter: granter.into(), + grantee: grantee.into(), + } + ); + Ok(allowance.allowance.unwrap()) + } + + /// Query allowances for grantee address with a given pagination + /// + /// see [PageRequest] for pagination + pub async fn _allowances( + &self, + grantee: impl Into, + pagination: Option, + ) -> Result, DaemonError> { + let allowances: cosmos_modules::feegrant::QueryAllowancesResponse = cosmos_query!( + self, + feegrant, + allowances, + QueryAllowancesRequest { + grantee: grantee.into(), + pagination: pagination + } + ); + Ok(allowances.allowances) + } +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/gov.rs b/cw-orch-daemon/src/queriers/grpc_old/gov.rs new file mode 100644 index 000000000..1fd4a5197 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/gov.rs @@ -0,0 +1,199 @@ +use crate::{cosmos_modules, error::DaemonError, Daemon}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cw_orch_core::environment::{Querier, QuerierGetter}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Querier for the Cosmos Gov module +/// All the async function are prefixed with `_` +pub struct Gov { + pub channel: Channel, + pub rt_handle: Option, +} + +impl Gov { + pub fn new(daemon: &Daemon) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl Querier for Gov { + type Error = DaemonError; +} + +impl QuerierGetter for Daemon { + fn querier(&self) -> Gov { + Gov::new(self) + } +} + +impl Gov { + /// Query proposal details by proposal id + pub async fn _proposal( + &self, + proposal_id: u64, + ) -> Result { + let proposal: cosmos_modules::gov::QueryProposalResponse = cosmos_query!( + self, + gov, + proposal, + QueryProposalRequest { + proposal_id: proposal_id, + } + ); + Ok(proposal.proposal.unwrap()) + } + + /// Query proposals based on given status + /// + /// see [PageRequest] for pagination + pub async fn _proposals( + &self, + proposal_status: GovProposalStatus, + voter: impl Into, + depositor: impl Into, + pagination: Option, + ) -> Result { + let proposals: cosmos_modules::gov::QueryProposalsResponse = cosmos_query!( + self, + gov, + proposals, + QueryProposalsRequest { + proposal_status: proposal_status as i32, + voter: voter.into(), + depositor: depositor.into(), + pagination: pagination + } + ); + Ok(proposals) + } + + /// Query voted information based on proposal_id for voter address + pub async fn _vote( + &self, + proposal_id: u64, + voter: impl Into, + ) -> Result { + let vote: cosmos_modules::gov::QueryVoteResponse = cosmos_query!( + self, + gov, + vote, + QueryVoteRequest { + proposal_id: proposal_id, + voter: voter.into() + } + ); + Ok(vote.vote.unwrap()) + } + + /// Query votes of a given proposal + /// + /// see [PageRequest] for pagination + pub async fn _votes( + &self, + proposal_id: impl Into, + pagination: Option, + ) -> Result { + let votes: cosmos_modules::gov::QueryVotesResponse = cosmos_query!( + self, + gov, + votes, + QueryVotesRequest { + proposal_id: proposal_id.into(), + pagination: pagination + } + ); + Ok(votes) + } + + /// Query all parameters of the gov module + pub async fn _params( + &self, + params_type: impl Into, + ) -> Result { + let params: cosmos_modules::gov::QueryParamsResponse = cosmos_query!( + self, + gov, + params, + QueryParamsRequest { + params_type: params_type.into() + } + ); + Ok(params) + } + + /// Query deposit information using proposal_id and depositor address + pub async fn _deposit( + &self, + proposal_id: u64, + depositor: impl Into, + ) -> Result { + let deposit: cosmos_modules::gov::QueryDepositResponse = cosmos_query!( + self, + gov, + deposit, + QueryDepositRequest { + proposal_id: proposal_id, + depositor: depositor.into() + } + ); + Ok(deposit.deposit.unwrap()) + } + + /// Query deposits of a proposal + /// + /// see [PageRequest] for pagination + pub async fn _deposits( + &self, + proposal_id: u64, + pagination: Option, + ) -> Result { + let deposits: cosmos_modules::gov::QueryDepositsResponse = cosmos_query!( + self, + gov, + deposits, + QueryDepositsRequest { + proposal_id: proposal_id, + pagination: pagination + } + ); + Ok(deposits) + } + + /// TallyResult queries the tally of a proposal vote. + pub async fn _tally_result( + &mut self, + proposal_id: u64, + ) -> Result { + let tally_result: cosmos_modules::gov::QueryTallyResultResponse = cosmos_query!( + self, + gov, + tally_result, + QueryTallyResultRequest { + proposal_id: proposal_id, + } + ); + Ok(tally_result.tally.unwrap()) + } +} + +/// Proposal status +#[allow(missing_docs)] +pub enum GovProposalStatus { + Unspecified = 0, + DepositPeriod = 1, + VotingPeriod = 2, + Passed = 3, + Rejected = 4, + Failed = 5, +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/ibc.rs b/cw-orch-daemon/src/queriers/grpc_old/ibc.rs new file mode 100644 index 000000000..986ee5ff7 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/ibc.rs @@ -0,0 +1,537 @@ +use crate::{cosmos_modules, error::DaemonError, Daemon}; +use cosmos_modules::ibc_channel; +use cosmrs::proto::ibc::{ + applications::transfer::v1::{DenomTrace, QueryDenomHashResponse, QueryDenomTraceResponse}, + core::{ + channel::v1::QueryPacketCommitmentResponse, + client::v1::{IdentifiedClientState, QueryClientStatesResponse}, + connection::v1::{ConnectionEnd, IdentifiedConnection, State}, + }, + lightclients::tendermint::v1::ClientState, +}; +use cw_orch_core::environment::{Querier, QuerierGetter}; +use prost::Message; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Querier for the Cosmos IBC module +/// All the async function are prefixed with `_` +pub struct Ibc { + pub channel: Channel, + pub rt_handle: Option, +} + +impl Ibc { + pub fn new(daemon: &Daemon) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl Querier for Ibc { + type Error = DaemonError; +} + +impl QuerierGetter for Daemon { + fn querier(&self) -> Ibc { + Ibc::new(self) + } +} + +impl Ibc { + // ### Transfer queries ### // + + /// Get the trace of a specific denom + pub async fn _denom_trace(&self, hash: String) -> Result { + let denom_trace: QueryDenomTraceResponse = cosmos_query!( + self, + ibc_transfer, + denom_trace, + QueryDenomTraceRequest { hash: hash } + ); + Ok(denom_trace.denom_trace.unwrap()) + } + + /// Get the hash of a specific denom from its trace + pub async fn _denom_hash(&self, trace: String) -> Result { + let denom_hash: QueryDenomHashResponse = cosmos_query!( + self, + ibc_transfer, + denom_hash, + QueryDenomHashRequest { trace: trace } + ); + Ok(denom_hash.hash) + } + + // ### Client queries ### + + /// Get all the IBC clients for this daemon + pub async fn _clients(&self) -> Result, DaemonError> { + let ibc_clients: QueryClientStatesResponse = cosmos_query!( + self, + ibc_client, + client_states, + QueryClientStatesRequest { pagination: None } + ); + Ok(ibc_clients.client_states) + } + + /// Get the state of a specific IBC client + pub async fn _client_state( + &self, + client_id: impl ToString, + // Add the necessary parameters here + ) -> Result { + let response: cosmos_modules::ibc_client::QueryClientStateResponse = cosmos_query!( + self, + ibc_client, + client_state, + QueryClientStateRequest { + client_id: client_id.to_string(), + } + ); + Ok(response) + } + + /// Get the consensus state of a specific IBC client + pub async fn _consensus_states( + &self, + client_id: impl ToString, + ) -> Result { + let client_id = client_id.to_string(); + let response: cosmos_modules::ibc_client::QueryConsensusStatesResponse = cosmos_query!( + self, + ibc_client, + consensus_states, + QueryConsensusStatesRequest { + client_id: client_id, + pagination: None, + } + ); + Ok(response) + } + + /// Get the consensus status of a specific IBC client + pub async fn _client_status( + &self, + client_id: impl ToString, + // Add the necessary parameters here + ) -> Result { + let response: cosmos_modules::ibc_client::QueryClientStatusResponse = cosmos_query!( + self, + ibc_client, + client_status, + QueryClientStatusRequest { + client_id: client_id.to_string(), + } + ); + Ok(response) + } + + /// Get the ibc client parameters + pub async fn _client_params( + &self, + ) -> Result { + let response: cosmos_modules::ibc_client::QueryClientParamsResponse = + cosmos_query!(self, ibc_client, client_params, QueryClientParamsRequest {}); + Ok(response) + } + + // ### Connection queries ### + + /// Query the IBC connections for a specific chain + pub async fn _connections(&self) -> Result, DaemonError> { + use cosmos_modules::ibc_connection::QueryConnectionsResponse; + + let ibc_connections: QueryConnectionsResponse = cosmos_query!( + self, + ibc_connection, + connections, + QueryConnectionsRequest { pagination: None } + ); + Ok(ibc_connections.connections) + } + + /// Search for open connections with a specific chain. + pub async fn _open_connections( + &self, + client_chain_id: impl ToString, + ) -> Result, DaemonError> { + let connections = self._connections().await?; + let mut open_connections = Vec::new(); + for connection in connections { + if connection.state() == State::Open { + open_connections.push(connection); + } + } + + // now search for the connections that use a client with the correct chain ids + let mut filtered_connections = Vec::new(); + for connection in open_connections { + let client_state = self._connection_client(&connection.id).await?; + if client_state.chain_id == client_chain_id.to_string() { + filtered_connections.push(connection); + } + } + + Ok(filtered_connections) + } + + // Get the information about a specific connection + pub async fn _connection_end( + &self, + connection_id: impl Into, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_connection::QueryConnectionResponse; + + let connection_id = connection_id.into(); + let ibc_client_connections: QueryConnectionResponse = cosmos_query!( + self, + ibc_connection, + connection, + QueryConnectionRequest { + connection_id: connection_id.clone() + } + ); + + Ok(ibc_client_connections.connection) + } + + /// Get all the connections for this client + pub async fn _client_connections( + &self, + client_id: impl Into, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_connection::QueryClientConnectionsResponse; + + let client_id = client_id.into(); + let ibc_client_connections: QueryClientConnectionsResponse = cosmos_query!( + self, + ibc_connection, + client_connections, + QueryClientConnectionsRequest { + client_id: client_id.clone() + } + ); + + Ok(ibc_client_connections.connection_paths) + } + + /// Get the (tendermint) client state for a specific connection + pub async fn _connection_client( + &self, + connection_id: impl Into, + ) -> Result { + use cosmos_modules::ibc_connection::QueryConnectionClientStateResponse; + let connection_id = connection_id.into(); + + let ibc_connection_client: QueryConnectionClientStateResponse = cosmos_query!( + self, + ibc_connection, + connection_client_state, + QueryConnectionClientStateRequest { + connection_id: connection_id.clone() + } + ); + + let client_state = + ibc_connection_client + .identified_client_state + .ok_or(DaemonError::ibc_err(format!( + "error identifying client for connection {}", + connection_id + )))?; + + let client_state = ClientState::decode(client_state.client_state.unwrap().value.as_slice()) + .map_err(|e| DaemonError::ibc_err(format!("error decoding client state: {}", e)))?; + + Ok(client_state) + } + + // ### Channel queries ### + + /// Get the channel for a specific port and channel id + pub async fn _channel( + &self, + port_id: impl Into, + channel_id: impl Into, + ) -> Result { + use cosmos_modules::ibc_channel::QueryChannelResponse; + + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_channel: QueryChannelResponse = cosmos_query!( + self, + ibc_channel, + channel, + QueryChannelRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + } + ); + + ibc_channel.channel.ok_or(DaemonError::ibc_err(format!( + "error fetching channel {} on port {}", + channel_id, port_id + ))) + } + + /// Get all the channels for a specific connection + pub async fn _connection_channels( + &self, + connection_id: impl Into, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_channel::QueryConnectionChannelsResponse; + + let connection_id = connection_id.into(); + let ibc_connection_channels: QueryConnectionChannelsResponse = cosmos_query!( + self, + ibc_channel, + connection_channels, + QueryConnectionChannelsRequest { + connection: connection_id.clone(), + pagination: None, + } + ); + + Ok(ibc_connection_channels.channels) + } + + /// Get the client state for a specific channel and port + pub async fn _channel_client_state( + &self, + port_id: impl Into, + channel_id: impl Into, + ) -> Result { + use cosmos_modules::ibc_channel::QueryChannelClientStateResponse; + + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_channel_client_state: QueryChannelClientStateResponse = cosmos_query!( + self, + ibc_channel, + channel_client_state, + QueryChannelClientStateRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + } + ); + + ibc_channel_client_state + .identified_client_state + .ok_or(DaemonError::ibc_err(format!( + "error identifying client for channel {} on port {}", + channel_id, port_id + ))) + } + + // ### Packet queries ### + + // Commitment + + /// Get all the packet commitments for a specific channel and port + pub async fn _packet_commitments( + &self, + port_id: impl Into, + channel_id: impl Into, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_channel::QueryPacketCommitmentsResponse; + + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_commitments: QueryPacketCommitmentsResponse = cosmos_query!( + self, + ibc_channel, + packet_commitments, + QueryPacketCommitmentsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + pagination: None, + } + ); + + Ok(ibc_packet_commitments.commitments) + } + + /// Get the packet commitment for a specific channel, port and sequence + pub async fn _packet_commitment( + &self, + port_id: impl Into, + channel_id: impl Into, + sequence: u64, + ) -> Result { + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_commitment: QueryPacketCommitmentResponse = cosmos_query!( + self, + ibc_channel, + packet_commitment, + QueryPacketCommitmentRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + sequence: sequence, + } + ); + + Ok(ibc_packet_commitment) + } + + // Receipt + + /// Returns if the packet is received on the connected chain. + pub async fn _packet_receipt( + &self, + port_id: impl Into, + channel_id: impl Into, + sequence: u64, + ) -> Result { + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_receipt: ibc_channel::QueryPacketReceiptResponse = cosmos_query!( + self, + ibc_channel, + packet_receipt, + QueryPacketReceiptRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + sequence: sequence, + } + ); + + Ok(ibc_packet_receipt.received) + } + + // Acknowledgement + + /// Get all the packet acknowledgements for a specific channel, port and commitment sequences + pub async fn _packet_acknowledgements( + &self, + port_id: impl Into, + channel_id: impl Into, + packet_commitment_sequences: Vec, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_channel::QueryPacketAcknowledgementsResponse; + + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_acknowledgements: QueryPacketAcknowledgementsResponse = cosmos_query!( + self, + ibc_channel, + packet_acknowledgements, + QueryPacketAcknowledgementsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + packet_commitment_sequences: packet_commitment_sequences, + pagination: None, + } + ); + + Ok(ibc_packet_acknowledgements.acknowledgements) + } + + /// Get the packet acknowledgement for a specific channel, port and sequence + pub async fn _packet_acknowledgement( + &self, + port_id: impl Into, + channel_id: impl Into, + sequence: u64, + ) -> Result, DaemonError> { + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_acknowledgement: ibc_channel::QueryPacketAcknowledgementResponse = cosmos_query!( + self, + ibc_channel, + packet_acknowledgement, + QueryPacketAcknowledgementRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + sequence: sequence, + } + ); + + Ok(ibc_packet_acknowledgement.acknowledgement) + } + + /// No acknowledgement exists on receiving chain for the given packet commitment sequence on sending chain. + /// Returns the packet sequences that have not yet been received. + pub async fn _unreceived_packets( + &self, + port_id: impl Into, + channel_id: impl Into, + packet_commitment_sequences: Vec, + ) -> Result, DaemonError> { + use cosmos_modules::ibc_channel::QueryUnreceivedPacketsResponse; + + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_unreceived: QueryUnreceivedPacketsResponse = cosmos_query!( + self, + ibc_channel, + unreceived_packets, + QueryUnreceivedPacketsRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + packet_commitment_sequences: packet_commitment_sequences, + } + ); + + Ok(ibc_packet_unreceived.sequences) + } + + /// Returns the acknowledgement sequences that have not yet been received. + /// Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain. + /// Returns the list of acknowledgement sequences that have not yet been received. + pub async fn _unreceived_acks( + &self, + port_id: impl Into, + channel_id: impl Into, + packet_ack_sequences: Vec, + ) -> Result, DaemonError> { + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let ibc_packet_unreceived: ibc_channel::QueryUnreceivedAcksResponse = cosmos_query!( + self, + ibc_channel, + unreceived_acks, + QueryUnreceivedAcksRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + packet_ack_sequences: packet_ack_sequences, + } + ); + + Ok(ibc_packet_unreceived.sequences) + } + + /// Returns the acknowledgement sequences that have not yet been received. + /// Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain. + /// Returns the list of acknowledgement sequences that have not yet been received. + pub async fn _next_sequence_receive( + &self, + port_id: impl Into, + channel_id: impl Into, + ) -> Result { + let port_id = port_id.into(); + let channel_id = channel_id.into(); + let next_receive: ibc_channel::QueryNextSequenceReceiveResponse = cosmos_query!( + self, + ibc_channel, + next_sequence_receive, + QueryNextSequenceReceiveRequest { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + } + ); + + Ok(next_receive.next_sequence_receive) + } +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/mod.rs b/cw-orch-daemon/src/queriers/grpc_old/mod.rs new file mode 100644 index 000000000..85b5586e8 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/mod.rs @@ -0,0 +1,45 @@ +//! # DaemonQuerier +//! +//! DaemonAsync queriers are gRPC query clients for the CosmosSDK modules. They can be used to query the different modules (Bank, Ibc, Authz, ...). +//! +//! ## Usage +//! +//! You will need to acquire a [gRPC channel](Channel) to a running CosmosSDK node to be able to use the queriers. +//! Here is an example of how to acquire one using the DaemonAsync builder. +//! +//! ```no_run +//! // require the querier you want to use, in this case Node +//! use cw_orch_daemon::{queriers::Node, DaemonAsync, networks, queriers::DaemonQuerier}; +//! # tokio_test::block_on(async { +//! // call the builder and configure it as you need +//! let daemon = DaemonAsync::builder() +//! .chain(networks::LOCAL_JUNO) +//! .build() +//! .await.unwrap(); +//! // now you can use the Node querier: +//! let node = Node::new(daemon.channel()); +//! let node_info = node.info(); +//! # }) +//! ``` + +mod auth; +mod bank; +mod cosmwasm; +mod feegrant; +mod gov; +mod ibc; +mod node; +mod staking; +mod tx; + +pub use auth::Auth; +pub use bank::Bank; +pub use cosmwasm::{CosmWasm, CosmWasmBase}; +pub use feegrant::Feegrant; +pub use ibc::Ibc; +pub use node::Node; +pub use tx::Tx; + +// this two containt structs that are helpers for the queries +pub use gov::*; +pub use staking::*; diff --git a/cw-orch-daemon/src/queriers/grpc_old/node.rs b/cw-orch-daemon/src/queriers/grpc_old/node.rs new file mode 100644 index 000000000..825122a52 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/node.rs @@ -0,0 +1,427 @@ +use std::{cmp::min, time::Duration}; + +use crate::{ + cosmos_modules, env::DaemonEnvVars, error::DaemonError, senders::query::QuerySender, + tx_resp::CosmTxResponse, DaemonBase, +}; + +use cosmrs::{ + proto::cosmos::{ + base::query::v1beta1::PageRequest, + tx::v1beta1::{OrderBy, SimulateResponse}, + }, + tendermint::{Block, Time}, +}; +use cosmwasm_std::BlockInfo; +use cw_orch_core::{ + environment::{NodeQuerier, Querier, QuerierGetter}, + log::query_target, +}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +/// Querier for the Tendermint node. +/// Supports queries for block and tx information +/// All the async function are prefixed with `_` +pub struct Node { + pub channel: Channel, + pub rt_handle: Option, +} + +impl Node { + pub fn new(daemon: &DaemonBase) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl QuerierGetter for DaemonBase { + fn querier(&self) -> Node { + Node::new(self) + } +} + +impl Querier for Node { + type Error = DaemonError; +} + +impl Node { + /// Returns node info + pub async fn _info( + &self, + ) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_node_info(cosmos_modules::tendermint::GetNodeInfoRequest {}) + .await? + .into_inner(); + + Ok(resp) + } + + /// Queries node syncing + pub async fn _syncing(&self) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_syncing(cosmos_modules::tendermint::GetSyncingRequest {}) + .await? + .into_inner(); + + Ok(resp.syncing) + } + + /// Returns latests block information + pub async fn _latest_block(&self) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_latest_block(cosmos_modules::tendermint::GetLatestBlockRequest {}) + .await? + .into_inner(); + + Ok(Block::try_from(resp.block.unwrap())?) + } + + /// Returns block information fetched by height + pub async fn _block_by_height(&self, height: u64) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_block_by_height(cosmos_modules::tendermint::GetBlockByHeightRequest { + height: height as i64, + }) + .await? + .into_inner(); + + Ok(Block::try_from(resp.block.unwrap())?) + } + + /// Return the average block time for the last 50 blocks or since inception + /// This is used to estimate the time when a tx will be included in a block + pub async fn _average_block_speed( + &self, + multiplier: Option, + ) -> Result { + // get latest block time and height + let mut latest_block = self._latest_block().await?; + let latest_block_time = latest_block.header.time; + let mut latest_block_height = latest_block.header.height.value(); + + while latest_block_height <= 1 { + // wait to get some blocks + tokio::time::sleep(Duration::from_secs(1)).await; + latest_block = self._latest_block().await?; + latest_block_height = latest_block.header.height.value(); + } + + // let avg period + let avg_period = min(latest_block_height - 1, 50); + + // get block time for block avg_period blocks ago + let block_avg_period_ago = self + ._block_by_height(latest_block_height - avg_period) + .await?; + let block_avg_period_ago_time = block_avg_period_ago.header.time; + + // calculate average block time + let average_block_time = latest_block_time.duration_since(block_avg_period_ago_time)?; + let average_block_time = average_block_time.div_f64(avg_period as f64); + + // multiply by multiplier if provided + let average_block_time = match multiplier { + Some(multiplier) => average_block_time.mul_f32(multiplier), + None => average_block_time, + }; + + Ok(average_block_time) + } + + /// Returns latests validator set + pub async fn _latest_validator_set( + &self, + pagination: Option, + ) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_latest_validator_set(cosmos_modules::tendermint::GetLatestValidatorSetRequest { + pagination, + }) + .await? + .into_inner(); + + Ok(resp) + } + + /// Returns latests validator set fetched by height + pub async fn _validator_set_by_height( + &self, + height: i64, + pagination: Option, + ) -> Result { + let mut client = + cosmos_modules::tendermint::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .get_validator_set_by_height( + cosmos_modules::tendermint::GetValidatorSetByHeightRequest { height, pagination }, + ) + .await? + .into_inner(); + + Ok(resp) + } + + /// Returns current block height + pub async fn _block_height(&self) -> Result { + let block = self._latest_block().await?; + Ok(block.header.height.value()) + } + + /// Returns the block timestamp (since unix epoch) in nanos + pub async fn _block_time(&self) -> Result { + let block = self._latest_block().await?; + Ok(block + .header + .time + .duration_since(Time::unix_epoch())? + .as_nanos()) + } + + /// Simulate TX + pub async fn _simulate_tx(&self, tx_bytes: Vec) -> Result { + let mut client = + cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone()); + #[allow(deprecated)] + let resp: SimulateResponse = client + .simulate(cosmos_modules::tx::SimulateRequest { tx: None, tx_bytes }) + .await? + .into_inner(); + let gas_used = resp.gas_info.unwrap().gas_used; + Ok(gas_used) + } + + /// Returns all the block info + pub async fn _block_info(&self) -> Result { + let block = self._latest_block().await?; + + block_to_block_info(block) + } + + /// Find TX by hash + pub async fn _find_tx(&self, hash: String) -> Result { + self._find_tx_with_retries(hash, DaemonEnvVars::max_tx_query_retries()) + .await + } + + /// Find TX by hash with a given amount of retries + pub async fn _find_tx_with_retries( + &self, + hash: String, + retries: usize, + ) -> Result { + let mut client = + cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone()); + + let request = cosmos_modules::tx::GetTxRequest { hash: hash.clone() }; + let mut block_speed = self._average_block_speed(Some(0.7)).await?; + let max_block_time = DaemonEnvVars::max_block_time(); + if let Some(max_time) = max_block_time { + block_speed = block_speed.min(max_time); + } else { + let min_block_time = DaemonEnvVars::min_block_time(); + block_speed = block_speed.max(min_block_time); + } + + for _ in 0..retries { + match client.get_tx(request.clone()).await { + Ok(tx) => { + let resp = tx.into_inner().tx_response.unwrap().into(); + log::debug!(target: &query_target(), "TX found: {:?}", resp); + return Ok(resp); + } + Err(err) => { + // increase wait time + block_speed = block_speed.mul_f64(1.6); + if let Some(max_time) = max_block_time { + block_speed = block_speed.min(max_time) + } + log::debug!(target: &query_target(), "TX not found with error: {:?}", err); + log::debug!(target: &query_target(), "Waiting {} milli-seconds", block_speed.as_millis()); + tokio::time::sleep(block_speed).await; + } + } + } + + // return error if tx not found by now + Err(DaemonError::TXNotFound(hash, retries)) + } + + /// Find TX by events + pub async fn _find_tx_by_events( + &self, + events: Vec, + page: Option, + order_by: Option, + ) -> Result, DaemonError> { + self._find_tx_by_events_with_retries( + events, + page, + order_by, + false, + DaemonEnvVars::max_tx_query_retries(), + ) + .await + } + + /// Find Tx by events + /// This function will consider that no transactions found is an error + /// This either returns a non empty vector or errors + pub async fn _find_some_tx_by_events( + &self, + events: Vec, + page: Option, + order_by: Option, + ) -> Result, DaemonError> { + self._find_tx_by_events_with_retries( + events, + page, + order_by, + true, + DaemonEnvVars::max_tx_query_retries(), + ) + .await + } + + /// Find TX by events with : + /// 1. Specify if an empty tx object is a valid response + /// 2. Specify a given amount of retries + pub async fn _find_tx_by_events_with_retries( + &self, + events: Vec, + page: Option, + order_by: Option, + retry_on_empty: bool, + retries: usize, + ) -> Result, DaemonError> { + let mut client = crate::cosmos_proto_patches::v0_50::tx::service_client::ServiceClient::new( + self.channel.clone(), + ); + + #[allow(deprecated)] + let request = crate::cosmos_proto_patches::v0_50::tx::GetTxsEventRequest { + events: events.clone(), + pagination: None, + order_by: order_by.unwrap_or(OrderBy::Desc).into(), + page: page.unwrap_or(0), + limit: 100, + query: events.join(" AND "), + }; + + for _ in 0..retries { + match client.get_txs_event(request.clone()).await { + Ok(tx) => { + let resp = tx.into_inner().tx_responses; + if retry_on_empty && resp.is_empty() { + log::debug!(target: &query_target(), "No TX found with events {:?}", events); + log::debug!(target: &query_target(), "Waiting 10s"); + tokio::time::sleep(Duration::from_secs(10)).await; + } else { + log::debug!( + target: &query_target(), + "TX found by events: {:?}", + resp.iter().map(|t| t.txhash.clone()) + ); + return Ok(resp.iter().map(|r| r.clone().into()).collect()); + } + } + Err(err) => { + log::debug!(target: &query_target(), "TX not found with error: {:?}", err); + log::debug!(target: &query_target(), "Waiting 10s"); + tokio::time::sleep(Duration::from_secs(10)).await; + } + } + } + // return error if tx not found by now + Err(DaemonError::TXNotFound( + format!("with events {:?}", events), + DaemonEnvVars::max_tx_query_retries(), + )) + } +} + +// Now we define traits + +impl NodeQuerier for Node { + type Response = CosmTxResponse; + + fn latest_block(&self) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._block_info()) + } + + fn block_by_height(&self, height: u64) -> Result { + let block = self + .rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._block_by_height(height))?; + + block_to_block_info(block) + } + + fn block_height(&self) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._block_height()) + } + + fn block_time(&self) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._block_time()) + } + + fn simulate_tx(&self, tx_bytes: Vec) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._simulate_tx(tx_bytes)) + } + + fn find_tx(&self, hash: String) -> Result { + self.rt_handle + .as_ref() + .ok_or(DaemonError::QuerierNeedRuntime)? + .block_on(self._find_tx(hash)) + } +} + +fn block_to_block_info(block: Block) -> Result { + let since_epoch = block.header.time.duration_since(Time::unix_epoch())?; + let time = cosmwasm_std::Timestamp::from_nanos(since_epoch.as_nanos() as u64); + Ok(cosmwasm_std::BlockInfo { + height: block.header.height.value(), + time, + chain_id: block.header.chain_id.to_string(), + }) +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/staking.rs b/cw-orch-daemon/src/queriers/grpc_old/staking.rs new file mode 100644 index 000000000..63d8b8860 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/staking.rs @@ -0,0 +1,347 @@ +use std::fmt::Display; + +use crate::{cosmos_modules, error::DaemonError, Daemon}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cosmwasm_std::{Addr, StdError}; +use cw_orch_core::environment::{Querier, QuerierGetter}; +use tokio::runtime::Handle; +use tonic::transport::Channel; + +use super::bank::cosmrs_to_cosmwasm_coin; + +/// Querier for the Cosmos Staking module +/// All the async function are prefixed with `_` +pub struct Staking { + pub channel: Channel, + pub rt_handle: Option, +} + +impl Staking { + pub fn new(daemon: &Daemon) -> Self { + Self { + channel: daemon.channel(), + rt_handle: Some(daemon.rt_handle.clone()), + } + } + + pub fn new_async(channel: Channel) -> Self { + Self { + channel, + rt_handle: None, + } + } +} + +impl Querier for Staking { + type Error = DaemonError; +} + +impl QuerierGetter for Daemon { + fn querier(&self) -> Staking { + Staking::new(self) + } +} +impl Staking { + /// Queries validator info for given validator address + pub async fn _validator( + &self, + validator_addr: impl Into, + ) -> Result { + let validator_response: cosmos_modules::staking::QueryValidatorResponse = cosmos_query!( + self, + staking, + validator, + QueryValidatorRequest { + validator_addr: validator_addr.into() + } + ); + + Ok(cosmrs_to_cosmwasm_validator( + validator_response.validator.unwrap(), + )?) + } + + /// Queries all validators that match the given status + /// + /// see [StakingBondStatus] for available statuses + pub async fn _validators( + &self, + status: StakingBondStatus, + ) -> Result, DaemonError> { + let validators: cosmos_modules::staking::QueryValidatorsResponse = cosmos_query!( + self, + staking, + validators, + QueryValidatorsRequest { + status: status.to_string(), + pagination: None, + } + ); + + Ok(validators + .validators + .into_iter() + .map(cosmrs_to_cosmwasm_validator) + .collect::>()?) + } + + /// Query validator delegations info for given validator + /// + /// see [PageRequest] for pagination + pub async fn _delegations( + &self, + validator_addr: impl Into, + pagination: Option, + ) -> Result, DaemonError> { + let validator_delegations: cosmos_modules::staking::QueryValidatorDelegationsResponse = cosmos_query!( + self, + staking, + validator_delegations, + QueryValidatorDelegationsRequest { + validator_addr: validator_addr.into(), + pagination: pagination + } + ); + Ok(validator_delegations + .delegation_responses + .into_iter() + .map(cosmrs_to_cosmwasm_delegation) + .collect::>()?) + } + + /// Query validator unbonding delegations of a validator + pub async fn _unbonding_delegations( + &self, + validator_addr: impl Into, + ) -> Result, DaemonError> { + let validator_unbonding_delegations: cosmos_modules::staking::QueryValidatorUnbondingDelegationsResponse = cosmos_query!( + self, + staking, + validator_unbonding_delegations, + QueryValidatorUnbondingDelegationsRequest { + validator_addr: validator_addr.into(), + pagination: None + } + ); + Ok(validator_unbonding_delegations.unbonding_responses) + } + + /// Query delegation info for given validator for a delegator + pub async fn _delegation( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let delegation: cosmos_modules::staking::QueryDelegationResponse = cosmos_query!( + self, + staking, + delegation, + QueryDelegationRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into() + } + ); + Ok(cosmrs_to_cosmwasm_delegation( + delegation.delegation_response.unwrap(), + )?) + } + + /// Query unbonding delegation info for given validator delegator + pub async fn _unbonding_delegation( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let unbonding_delegation: cosmos_modules::staking::QueryUnbondingDelegationResponse = cosmos_query!( + self, + staking, + unbonding_delegation, + QueryUnbondingDelegationRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into() + } + ); + Ok(unbonding_delegation.unbond.unwrap()) + } + + /// Query all delegator delegations of a given delegator address + /// + /// see [PageRequest] for pagination + pub async fn _delegator_delegations( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result { + let delegator_delegations: cosmos_modules::staking::QueryDelegatorDelegationsResponse = cosmos_query!( + self, + staking, + delegator_delegations, + QueryDelegatorDelegationsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + } + ); + Ok(delegator_delegations) + } + + /// Queries all unbonding delegations of a given delegator address. + /// + /// see [PageRequest] for pagination + pub async fn _delegator_unbonding_delegations( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result + { + let delegator_unbonding_delegations: cosmos_modules::staking::QueryDelegatorUnbondingDelegationsResponse = cosmos_query!( + self, + staking, + delegator_unbonding_delegations, + QueryDelegatorUnbondingDelegationsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + } + ); + Ok(delegator_unbonding_delegations) + } + + /// Query redelegations of a given address + /// + /// see [PageRequest] for pagination + pub async fn _redelegations( + &self, + delegator_addr: impl Into, + src_validator_addr: impl Into, + dst_validator_addr: impl Into, + pagination: Option, + ) -> Result { + let redelegations: cosmos_modules::staking::QueryRedelegationsResponse = cosmos_query!( + self, + staking, + redelegations, + QueryRedelegationsRequest { + delegator_addr: delegator_addr.into(), + src_validator_addr: src_validator_addr.into(), + dst_validator_addr: dst_validator_addr.into(), + pagination: pagination + } + ); + Ok(redelegations) + } + + /// Query delegator validators info for given delegator address. + pub async fn _delegator_validator( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let delegator_validator: cosmos_modules::staking::QueryDelegatorValidatorResponse = cosmos_query!( + self, + staking, + delegator_validator, + QueryDelegatorValidatorRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into(), + } + ); + Ok(delegator_validator) + } + + /// Query delegator validators info for given delegator address + /// + /// see [PageRequest] for pagination + pub async fn _delegator_validators( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result { + let delegator_validators: cosmos_modules::staking::QueryDelegatorValidatorsResponse = cosmos_query!( + self, + staking, + delegator_validators, + QueryDelegatorValidatorsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + } + ); + + Ok(delegator_validators) + } + + /// Query historical info info for given height + pub async fn _historical_info( + &self, + height: i64, + ) -> Result { + let historical_info: cosmos_modules::staking::QueryHistoricalInfoResponse = cosmos_query!( + self, + staking, + historical_info, + QueryHistoricalInfoRequest { height: height } + ); + Ok(historical_info) + } + + /// Query the pool info + pub async fn _pool(&self) -> Result { + let pool: cosmos_modules::staking::QueryPoolResponse = + cosmos_query!(self, staking, pool, QueryPoolRequest {}); + Ok(pool) + } + + /// Query staking parameters + pub async fn _params( + &self, + ) -> Result { + let params: cosmos_modules::staking::QueryParamsResponse = + cosmos_query!(self, staking, params, QueryParamsRequest {}); + Ok(params) + } +} + +/// Staking bond statuses +pub enum StakingBondStatus { + /// UNSPECIFIED defines an invalid validator status. + Unspecified = 0, + /// UNBONDED defines a validator that is not bonded. + Unbonded = 1, + /// UNBONDING defines a validator that is unbonding. + Unbonding = 2, + /// BONDED defines a validator that is bonded. + Bonded = 3, +} + +impl Display for StakingBondStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match self { + StakingBondStatus::Unspecified => "BOND_STATUS_UNSPECIFIED", + StakingBondStatus::Unbonded => "BOND_STATUS_UNBONDED", + StakingBondStatus::Unbonding => "BOND_STATUS_UNBONDING", + StakingBondStatus::Bonded => "BOND_STATUS_BONDED", + }; + write!(f, "{}", str) + } +} + +pub fn cosmrs_to_cosmwasm_validator( + validator: cosmrs::proto::cosmos::staking::v1beta1::Validator, +) -> Result { + let comission = validator.commission.unwrap().commission_rates.unwrap(); + Ok(cosmwasm_std::Validator { + address: validator.operator_address, + commission: comission.rate.parse()?, + max_commission: comission.max_rate.parse()?, + max_change_rate: comission.max_change_rate.parse()?, + }) +} + +pub fn cosmrs_to_cosmwasm_delegation( + delegation_response: cosmrs::proto::cosmos::staking::v1beta1::DelegationResponse, +) -> Result { + let delegation = delegation_response.delegation.unwrap(); + Ok(cosmwasm_std::Delegation { + delegator: Addr::unchecked(delegation.delegator_address), + validator: delegation.validator_address, + amount: cosmrs_to_cosmwasm_coin(delegation_response.balance.unwrap())?, + }) +} diff --git a/cw-orch-daemon/src/queriers/grpc_old/tx.rs b/cw-orch-daemon/src/queriers/grpc_old/tx.rs new file mode 100644 index 000000000..9853a16b3 --- /dev/null +++ b/cw-orch-daemon/src/queriers/grpc_old/tx.rs @@ -0,0 +1,35 @@ +// Only a simple implementation to not overload the tx builder + +use cosmrs::{proto::cosmos::base::abci::v1beta1::TxResponse, tx::Raw}; +use tonic::transport::Channel; + +use crate::{cosmos_modules, queriers::DaemonQuerier, DaemonError}; + +/// Queries for Cosmos Bank Module +pub struct Tx { + channel: Channel, +} + +impl DaemonQuerier for Tx { + fn new(channel: Channel) -> Self { + Self { channel } + } +} + +impl Tx { + /// Query spendable balance for address + pub async fn broadcast(&self, tx: Raw) -> Result { + let mut client = + cosmos_modules::tx::service_client::ServiceClient::new(self.channel.clone()); + + let resp = client + .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { + tx_bytes: tx.to_bytes()?, + mode: cosmos_modules::tx::BroadcastMode::Sync.into(), + }) + .await? + .into_inner(); + + Ok(resp.tx_response.unwrap()) + } +} diff --git a/cw-orch-daemon/src/queriers/rpc.rs b/cw-orch-daemon/src/queriers/rpc.rs new file mode 100644 index 000000000..cef0ed1a5 --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc.rs @@ -0,0 +1,32 @@ + +/// macro for constructing and performing a query on a CosmosSDK module. +#[macro_export] +macro_rules! cosmos_rpc_query { + ($self:ident, $module:ident, $type_url:literal, $request_type:ident { $($field:ident : $value:expr),* $(,)? }, $request_resp:ident, ) => { + { + use $crate::cosmos_modules::$module::{ + $request_resp, $request_type, + }; + use ::cosmrs::rpc::Client; + use ::cosmrs::tx::MessageExt; + use ::prost::Message; + + let request = $request_type { $($field : $value),* }; + let response = $self.client.abci_query( + Some($type_url.to_string()), + request.to_bytes()?, + None, + // Don't request proof, we don't need it + false, + ).await?; + let decoded_response = $request_resp::decode(response.value.as_slice())?; + ::log::trace!( + "cosmos_query: {:?} resulted in: {:?}", + request, + decoded_response + ); + + decoded_response + } +}; +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/auth.rs b/cw-orch-daemon/src/queriers/rpc_old/auth.rs new file mode 100644 index 000000000..c09543449 --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/auth.rs @@ -0,0 +1,32 @@ +// Only a simple implementation to not overload the tx builder + +use cosmrs::rpc::HttpClient; + +use crate::{cosmos_rpc_query, queriers::DaemonQuerier, DaemonError}; + +/// Queries for Cosmos Auth Module +pub struct Auth { + client: HttpClient, +} + +impl DaemonQuerier for Auth { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl Auth { + /// Query the account + pub async fn account(&self, address: impl Into) -> Result, DaemonError> { + let resp = cosmos_rpc_query!( + self, + auth, + "/cosmos.auth.v1beta1.Query/Account", + QueryAccountRequest { + address: address.into() + }, + QueryAccountResponse, + ); + Ok(resp.account.unwrap().value) + } +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/bank.rs b/cw-orch-daemon/src/queriers/rpc_old/bank.rs new file mode 100644 index 000000000..d5251546a --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/bank.rs @@ -0,0 +1,150 @@ +use crate::{cosmos_modules, cosmos_rpc_query, error::DaemonError, queriers::DaemonQuerier}; +use cosmrs::{ + proto::cosmos::base::{query::v1beta1::PageRequest, v1beta1::Coin}, + rpc::HttpClient, +}; + +/// Queries for Cosmos Bank Module +pub struct Bank { + client: HttpClient, +} + +impl DaemonQuerier for Bank { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl Bank { + /// Query the bank balance of a given address + /// If denom is None, returns all balances + pub async fn balance( + &self, + address: impl Into, + denom: Option, + ) -> Result, DaemonError> { + match denom { + Some(denom) => { + let balance_response = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/Balance", + QueryBalanceRequest { + address: address.into(), + denom: denom, + }, + QueryBalanceResponse, + ); + let coin = balance_response.balance.unwrap(); + Ok(vec![coin]) + } + None => { + let balance_response = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/AllBalances", + QueryAllBalancesRequest { + address: address.into(), + pagination: None, + }, + QueryAllBalancesResponse, + ); + + let coins = balance_response.balances; + Ok(coins.into_iter().collect()) + } + } + } + + /// Query spendable balance for address + pub async fn spendable_balances( + &self, + address: impl Into, + ) -> Result, DaemonError> { + let spendable_balances = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/SpendableBalances", + QuerySpendableBalancesRequest { + address: address.into(), + pagination: None, + }, + QuerySpendableBalancesResponse, + ); + Ok(spendable_balances.balances) + } + + /// Query total supply in the bank + pub async fn total_supply(&self) -> Result, DaemonError> { + let total_supply = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/TotalSupply", + QueryTotalSupplyRequest { pagination: None }, + QueryTotalSupplyResponse, + ); + Ok(total_supply.supply) + } + + /// Query total supply in the bank for a denom + pub async fn supply_of(&self, denom: impl Into) -> Result { + let supply_of = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/SupplyOf", + QuerySupplyOfRequest { + denom: denom.into() + }, + QuerySupplyOfResponse, + ); + Ok(supply_of.amount.unwrap()) + } + + /// Query params + pub async fn params(&self) -> Result { + let params = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/Params", + QueryParamsRequest {}, + QueryParamsResponse, + ); + Ok(params.params.unwrap()) + } + + /// Query denom metadata + pub async fn denom_metadata( + &self, + denom: impl Into, + ) -> Result { + let denom_metadata = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/DenomMetadata", + QueryDenomMetadataRequest { + denom: denom.into() + }, + QueryDenomMetadataResponse, + ); + Ok(denom_metadata.metadata.unwrap()) + } + + /// Query denoms metadata with pagination + /// + /// see [PageRequest] for pagination + pub async fn denoms_metadata( + &self, + pagination: Option, + ) -> Result, DaemonError> { + let denoms_metadata = cosmos_rpc_query!( + self, + bank, + "/cosmos.bank.v1beta1.Query/DenomsMetadata", + QueryDenomsMetadataRequest { + pagination: pagination + }, + QueryDenomsMetadataResponse, + ); + Ok(denoms_metadata.metadatas) + } +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/cosmwasm.rs b/cw-orch-daemon/src/queriers/rpc_old/cosmwasm.rs new file mode 100644 index 000000000..6a4474717 --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/cosmwasm.rs @@ -0,0 +1,223 @@ +use crate::{cosmos_modules, cosmos_rpc_query, error::DaemonError, queriers::DaemonQuerier}; +use cosmrs::{proto::cosmos::base::query::v1beta1::PageRequest, rpc::HttpClient}; + +/// Querier for the CosmWasm SDK module +pub struct CosmWasm { + client: HttpClient, +} + +impl DaemonQuerier for CosmWasm { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl CosmWasm { + /// Query code_id by hash + pub async fn code_id_hash(&self, code_id: u64) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/Code", + QueryCodeRequest { code_id: code_id }, + QueryCodeResponse, + ); + + let contract_hash = resp.code_info.unwrap().data_hash; + let on_chain_hash = base16::encode_lower(&contract_hash); + Ok(on_chain_hash) + } + + /// Query contract info + pub async fn contract_info( + &self, + address: impl Into, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/ContractInfo", + QueryContractInfoRequest { + address: address.into(), + }, + QueryContractInfoResponse, + ); + + let contract_info = resp.contract_info.unwrap(); + Ok(contract_info) + } + + /// Query contract history + pub async fn contract_history( + &self, + address: impl Into, + pagination: Option, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/ContractHistory", + QueryContractHistoryRequest { + address: address.into(), + pagination: pagination, + }, + QueryContractHistoryResponse, + ); + + Ok(resp) + } + + /// Query contract state + pub async fn contract_state( + &self, + address: impl Into, + query_data: Vec, + ) -> Result, DaemonError> { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/SmartContractState", + QuerySmartContractStateRequest { + address: address.into(), + query_data: query_data, + }, + QuerySmartContractStateResponse, + ); + + Ok(resp.data) + } + + /// Query all contract state + pub async fn all_contract_state( + &self, + address: impl Into, + pagination: Option, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/AllContractState", + QueryAllContractStateRequest { + address: address.into(), + pagination: pagination, + }, + QueryAllContractStateResponse, + ); + Ok(resp) + } + + /// Query code + pub async fn code( + &self, + code_id: u64, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/Code", + QueryCodeRequest { code_id: code_id }, + QueryCodeResponse, + ); + + Ok(resp.code_info.unwrap()) + } + + /// Query code bytes + pub async fn code_data(&self, code_id: u64) -> Result, DaemonError> { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/Code", + QueryCodeRequest { code_id: code_id }, + QueryCodeResponse, + ); + + Ok(resp.data) + } + + /// Query codes + pub async fn codes( + &self, + pagination: Option, + ) -> Result, DaemonError> { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/Codes", + QueryCodesRequest { + pagination: pagination + }, + QueryCodesResponse, + ); + + Ok(resp.code_infos) + } + + /// Query pinned codes + pub async fn pinned_codes( + &self, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/PinnedCodes", + QueryPinnedCodesRequest { pagination: None }, + QueryPinnedCodesResponse, + ); + Ok(resp) + } + + /// Query contracts by code + pub async fn contract_by_codes( + &self, + code_id: u64, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/ContractsByCode", + QueryContractsByCodeRequest { + code_id: code_id, + pagination: None, + }, + QueryContractsByCodeResponse, + ); + + Ok(resp) + } + + /// Query raw contract state + pub async fn contract_raw_state( + &self, + address: impl Into, + query_data: Vec, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/RawContractState", + QueryRawContractStateRequest { + address: address.into(), + query_data: query_data, + }, + QueryRawContractStateResponse, + ); + + Ok(resp) + } + + /// Query params + pub async fn params( + &self, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + cosmwasm, + "/cosmwasm.wasm.v1.Query/Params", + QueryParamsRequest {}, + QueryParamsResponse, + ); + + Ok(resp) + } +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/mod.rs b/cw-orch-daemon/src/queriers/rpc_old/mod.rs new file mode 100644 index 000000000..37d714af7 --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/mod.rs @@ -0,0 +1,15 @@ +mod auth; +mod bank; +mod cosmwasm; +mod node; +mod staking; +mod tx; + +pub use auth::Auth; +pub use bank::Bank; +pub use cosmwasm::CosmWasm; +pub use node::Node; +pub use staking::Staking; +pub use tx::Tx; +// pub use feegrant::Feegrant; +// pub use ibc::Ibc; diff --git a/cw-orch-daemon/src/queriers/rpc_old/node.rs b/cw-orch-daemon/src/queriers/rpc_old/node.rs new file mode 100644 index 000000000..54d40bbfa --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/node.rs @@ -0,0 +1,228 @@ +use std::{cmp::min, time::Duration}; + +use cosmrs::tendermint::block::Height; +use cosmrs::tendermint::node; +use cosmrs::{ + proto::cosmos::base::query::v1beta1::PageRequest, + rpc::{Client, HttpClient}, + tendermint::{Block, Time}, +}; + +use crate::{ + cosmos_modules, cosmos_rpc_query, + error::DaemonError, + queriers::{DaemonQuerier, MAX_TX_QUERY_RETRIES}, + tx_resp::CosmTxResponse, +}; + +/// Querier for the Tendermint node. +/// Supports queries for block and tx information +/// @TODO: all tendermint queries should use the tendermint-rpc explicitly instead of hitting the tendermint node with the typed queries. +pub struct Node { + client: HttpClient, +} + +impl DaemonQuerier for Node { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl Node { + /// Returns node info + pub async fn info(&self) -> Result { + let stats = self.client.status().await?; + + Ok(stats.node_info) + } + + /// Queries node syncing + pub async fn syncing(&self) -> Result { + let resp = cosmos_rpc_query!( + self, + tendermint, + "/cosmos.base.tendermint.v1beta1.Service/GetSyncing", + GetSyncingRequest {}, + GetSyncingResponse, + ); + + Ok(resp.syncing) + } + + /// Returns latest block information + pub async fn latest_block(&self) -> Result { + let resp = self.client.latest_block().await?; + + Ok(resp.block) + } + + /// Returns block information fetched by height + pub async fn block_by_height(&self, height: u64) -> Result { + let resp = self.client.block(Height::try_from(height).unwrap()).await?; + + Ok(resp.block) + } + + /// Return the average block time for the last 50 blocks or since inception + /// This is used to estimate the time when a tx will be included in a block + pub async fn average_block_speed(&self, multiplier: Option) -> Result { + // get latest block time and height + let mut latest_block = self.latest_block().await?; + let latest_block_time = latest_block.header.time; + let mut latest_block_height = latest_block.header.height.value(); + + while latest_block_height <= 1 { + // wait to get some blocks + tokio::time::sleep(Duration::from_secs(1)).await; + latest_block = self.latest_block().await?; + latest_block_height = latest_block.header.height.value(); + } + + // let avg period + let avg_period = min(latest_block_height - 1, 50); + + // get block time for block avg_period blocks ago + let block_avg_period_ago = self + .block_by_height(latest_block_height - avg_period) + .await?; + let block_avg_period_ago_time = block_avg_period_ago.header.time; + + // calculate average block time + let average_block_time = latest_block_time.duration_since(block_avg_period_ago_time)?; + let average_block_time = average_block_time.as_secs() / avg_period; + + // multiply by multiplier if provided + let average_block_time = match multiplier { + Some(multiplier) => (average_block_time as f32 * multiplier) as u64, + None => average_block_time, + }; + + Ok(std::cmp::max(average_block_time, 1)) + } + + /// Returns latests validator set + pub async fn latest_validator_set( + &self, + pagination: Option, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + tendermint, + "/cosmos.base.tendermint.v1beta1.Service/GetLatestValidatorSet", + GetLatestValidatorSetRequest { + pagination: pagination, + }, + GetLatestValidatorSetResponse, + ); + + Ok(resp) + } + + /// Returns latests validator set fetched by height + pub async fn validator_set_by_height( + &self, + height: i64, + pagination: Option, + ) -> Result { + let resp = cosmos_rpc_query!( + self, + tendermint, + "/cosmos.base.tendermint.v1beta1.Service/GetValidatorSetByHeight", + GetValidatorSetByHeightRequest { + height: height, + pagination: pagination + }, + GetValidatorSetByHeightResponse, + ); + + Ok(resp) + } + + /// Returns current block height + pub async fn block_height(&self) -> Result { + let block = self.latest_block().await?; + Ok(block.header.height.value()) + } + + /// Returns the block timestamp (since unix epoch) in nanos + pub async fn block_time(&self) -> Result { + let block = self.latest_block().await?; + Ok(block + .header + .time + .duration_since(Time::unix_epoch())? + .as_nanos()) + } + + /// Simulate TX + pub async fn simulate_tx(&self, tx_bytes: Vec) -> Result { + log::debug!("Simulating transaction"); + + // We use this allow deprecated for the tx field of the simulate request (but we set it to None, so that's ok) + #[allow(deprecated)] + let resp = cosmos_rpc_query!( + self, + tx, + "/cosmos.tx.v1beta1.Service/Simulate", + SimulateRequest { + tx: None, + tx_bytes: tx_bytes + }, + SimulateResponse, + ); + + let gas_used = resp.gas_info.unwrap().gas_used; + + log::debug!("Gas used in simulation: {:?}", gas_used); + Ok(gas_used) + } + + /// Returns all the block info + pub async fn block_info(&self) -> Result { + let block = self.latest_block().await?; + let since_epoch = block.header.time.duration_since(Time::unix_epoch())?; + let time = cosmwasm_std::Timestamp::from_nanos(since_epoch.as_nanos() as u64); + Ok(cosmwasm_std::BlockInfo { + height: block.header.height.value(), + time, + chain_id: block.header.chain_id.to_string(), + }) + } + + /// Find TX by hash + pub async fn find_tx(&self, hash: String) -> Result { + self.find_tx_with_retries(hash, MAX_TX_QUERY_RETRIES).await + } + + /// Find TX by hash with a given amount of retries + pub async fn find_tx_with_retries( + &self, + hash: String, + retries: usize, + ) -> Result { + let mut block_speed = self.average_block_speed(Some(0.7)).await?; + + let hexed_hash = hex::decode(hash.clone())?.try_into().unwrap(); + + for _ in 0..retries { + let tx_r = self.client.tx(hexed_hash, false).await; + + match tx_r { + Ok(tx) => { + log::debug!("TX found: {:?}", tx); + return Ok(tx.into()); + } + Err(err) => { + // increase wait time + block_speed = (block_speed as f64 * 1.6) as u64; + log::debug!("TX not found with error: {:?}", err); + log::debug!("Waiting {block_speed} seconds"); + tokio::time::sleep(Duration::from_secs(block_speed)).await; + } + } + } + + // return error if tx not found by now + Err(DaemonError::TXNotFound(hash, retries)) + } +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/staking.rs b/cw-orch-daemon/src/queriers/rpc_old/staking.rs new file mode 100644 index 000000000..6bda5611d --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/staking.rs @@ -0,0 +1,305 @@ +use crate::{cosmos_modules, cosmos_rpc_query, error::DaemonError}; +use cosmrs::{proto::cosmos::base::query::v1beta1::PageRequest, rpc::HttpClient}; + +use crate::queriers::DaemonQuerier; + +/// Querier for the Cosmos Staking module +pub struct Staking { + client: HttpClient, +} + +impl DaemonQuerier for Staking { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl Staking { + /// Queries validator info for given validator address + pub async fn validator( + &self, + validator_addr: impl Into, + ) -> Result { + let validator = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Validator", + QueryValidatorRequest { + validator_addr: validator_addr.into() + }, + QueryValidatorResponse, + ); + Ok(validator.validator.unwrap()) + } + + /// Queries all validators that match the given status + /// + /// see [StakingBondStatus] for available statuses + pub async fn validators( + &self, + status: StakingBondStatus, + ) -> Result, DaemonError> { + let validators = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Validators", + QueryValidatorsRequest { + status: status.to_string(), + pagination: None, + }, + QueryValidatorsResponse, + ); + Ok(validators.validators) + } + + /// Query validator delegations info for given validator + /// + /// see [PageRequest] for pagination + pub async fn delegations( + &self, + validator_addr: impl Into, + pagination: Option, + ) -> Result, DaemonError> { + let validator_delegations = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/ValidatorDelegations", + QueryValidatorDelegationsRequest { + validator_addr: validator_addr.into(), + pagination: pagination + }, + QueryValidatorDelegationsResponse, + ); + Ok(validator_delegations.delegation_responses) + } + + /// Query validator unbonding delegations of a validator + pub async fn unbonding_delegations( + &self, + validator_addr: impl Into, + ) -> Result, DaemonError> { + let validator_unbonding_delegations = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations", + QueryValidatorUnbondingDelegationsRequest { + validator_addr: validator_addr.into(), + pagination: None + }, + QueryValidatorUnbondingDelegationsResponse, + ); + Ok(validator_unbonding_delegations.unbonding_responses) + } + + /// Query delegation info for given validator for a delegator + pub async fn delegation( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let delegation = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Delegation", + QueryDelegationRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into() + }, + QueryDelegationResponse, + ); + Ok(delegation.delegation_response.unwrap()) + } + + /// Query unbonding delegation info for given validator delegator + pub async fn unbonding_delegation( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let unbonding_delegation = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/UnbondingDelegation", + QueryUnbondingDelegationRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into() + }, + QueryUnbondingDelegationResponse, + ); + Ok(unbonding_delegation.unbond.unwrap()) + } + + /// Query all delegator delegations of a given delegator address + /// + /// see [PageRequest] for pagination + pub async fn delegator_delegations( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result { + let delegator_delegations = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/DelegatorDelegations", + QueryDelegatorDelegationsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + }, + QueryDelegatorDelegationsResponse, + ); + Ok(delegator_delegations) + } + + /// Queries all unbonding delegations of a given delegator address. + /// + /// see [PageRequest] for pagination + pub async fn delegator_unbonding_delegations( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result + { + let delegator_unbonding_delegations = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations", + QueryDelegatorUnbondingDelegationsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + }, + QueryDelegatorUnbondingDelegationsResponse, + ); + Ok(delegator_unbonding_delegations) + } + + /// Query redelegations of a given address + /// + /// see [PageRequest] for pagination + pub async fn redelegations( + &self, + delegator_addr: impl Into, + src_validator_addr: impl Into, + dst_validator_addr: impl Into, + pagination: Option, + ) -> Result { + let redelegations = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Redelegations", + QueryRedelegationsRequest { + delegator_addr: delegator_addr.into(), + src_validator_addr: src_validator_addr.into(), + dst_validator_addr: dst_validator_addr.into(), + pagination: pagination + }, + QueryRedelegationsResponse, + ); + Ok(redelegations) + } + + /// Query delegator validators info for given delegator address. + pub async fn delegator_validator( + &self, + validator_addr: impl Into, + delegator_addr: impl Into, + ) -> Result { + let delegator_validator = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/DelegatorValidator", + QueryDelegatorValidatorRequest { + validator_addr: validator_addr.into(), + delegator_addr: delegator_addr.into(), + }, + QueryDelegatorValidatorResponse, + ); + Ok(delegator_validator) + } + + /// Query delegator validators info for given delegator address + /// + /// see [PageRequest] for pagination + pub async fn delegator_validators( + &self, + delegator_addr: impl Into, + pagination: Option, + ) -> Result { + let delegator_validators = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/DelegatorValidators", + QueryDelegatorValidatorsRequest { + delegator_addr: delegator_addr.into(), + pagination: pagination + }, + QueryDelegatorValidatorsResponse, + ); + + Ok(delegator_validators) + } + + /// Query historical info info for given height + pub async fn historical_info( + &self, + height: i64, + ) -> Result { + let historical_info = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/HistoricalInfo", + QueryHistoricalInfoRequest { height: height }, + QueryHistoricalInfoResponse, + ); + Ok(historical_info) + } + + /// Query the pool info + pub async fn pool(&self) -> Result { + let pool = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Pool", + QueryPoolRequest {}, + QueryPoolResponse, + ); + Ok(pool) + } + + /// Query staking parameters + pub async fn params( + &self, + ) -> Result { + let params = cosmos_rpc_query!( + self, + staking, + "/cosmos.staking.v1beta1.Query/Params", + QueryParamsRequest {}, + QueryParamsResponse, + ); + Ok(params) + } +} + +/// Staking bond statuses +pub enum StakingBondStatus { + /// UNSPECIFIED defines an invalid validator status. + Unspecified = 0, + /// UNBONDED defines a validator that is not bonded. + Unbonded = 1, + /// UNBONDING defines a validator that is unbonding. + Unbonding = 2, + /// BONDED defines a validator that is bonded. + Bonded = 3, +} + +impl ToString for StakingBondStatus { + /// Convert to string + fn to_string(&self) -> String { + match self { + StakingBondStatus::Unspecified => "BOND_STATUS_UNSPECIFIED".to_string(), + StakingBondStatus::Unbonded => "BOND_STATUS_UNBONDED".to_string(), + StakingBondStatus::Unbonding => "BOND_STATUS_UNBONDING".to_string(), + StakingBondStatus::Bonded => "BOND_STATUS_BONDED".to_string(), + } + } +} diff --git a/cw-orch-daemon/src/queriers/rpc_old/tx.rs b/cw-orch-daemon/src/queriers/rpc_old/tx.rs new file mode 100644 index 000000000..aa4e68fad --- /dev/null +++ b/cw-orch-daemon/src/queriers/rpc_old/tx.rs @@ -0,0 +1,41 @@ +// Only a simple implementation to not overload the tx builder + +use cosmrs::{proto::cosmos::base::abci::v1beta1::TxResponse, rpc::HttpClient, tx::Raw}; + +use crate::{queriers::DaemonQuerier, DaemonError}; +use cosmrs::rpc::Client; + +/// Queries for Cosmos Bank Module +pub struct Tx { + client: HttpClient, +} + +impl DaemonQuerier for Tx { + fn new(client: HttpClient) -> Self { + Self { client } + } +} + +impl Tx { + /// Query spendable balance for address + pub async fn broadcast(&self, tx: Raw) -> Result { + let resp = self.client.broadcast_tx_commit(tx.to_bytes()?).await?; + + let check = resp.check_tx; + Ok(TxResponse { + height: resp.height.into(), + txhash: resp.hash.to_string(), + codespace: check.codespace, + code: check.code.into(), + data: "".to_string(), + raw_log: check.log, + logs: vec![], + info: check.info, + gas_wanted: check.gas_wanted, + gas_used: check.gas_used, + tx: None, + timestamp: "".to_string(), + events: vec![], + }) + } +} diff --git a/cw-orch-daemon/src/senders/client.rs b/cw-orch-daemon/src/senders/client.rs new file mode 100644 index 000000000..591cd2463 --- /dev/null +++ b/cw-orch-daemon/src/senders/client.rs @@ -0,0 +1,294 @@ +use std::future::Future; + +use crate::error::DaemonError; +use cw_orch_core::environment::ChainInfoOwned; +/// A client for sending messages to a CosmosSDK chain. +/// If the `grpc` feature is enabled, the client will use gRPC to send messages. +/// If the `rpc` feature is enabled, the client will use HTTP to send messages. +#[derive(Debug, Clone)] +pub struct CosmosClient { + // TODO: if both enabled, default to RPC + #[cfg(feature = "grpc")] + inner: tonic::transport::Channel, + #[cfg(feature = "rpc")] + inner: HttpClient, +} + +impl CosmosClient { + /// Create a gRPC channel from the chain info + pub async fn from_chain_info(chain_info: &ChainInfoOwned) -> Result { + // RPC overwrites gRPC if both features are enabled + #[cfg(feature = "rpc")] + if cfg!(feature = "rpc") { + return Ok(Self { + inner: self::rpc::RpcChannel::connect(&chain_info.grpc_urls, &chain_info.chain_id) + .await?, + }); + } + + #[cfg(feature = "grpc")] + Ok(Self { + inner: self::grpc::GrpcChannel::connect( + &chain_info.grpc_urls, + &chain_info.chain_id, + ) + .await?, + }) + } +} + +trait Connect { + type Connection; + /// Connect to an endpoint and return a client + fn connect( + urls: &[String], + chain_id: &str, + ) -> impl Future> + Send; +} + +mod rpc { + use cosmrs::rpc::{Client, HttpClient}; + + use crate::error::DaemonError; + + use super::Connect; + + /// A helper for constructing a gRPC channel + pub struct RpcChannel {} + + impl Connect for RpcChannel { + type Connection = HttpClient; + + /// Connect to any of the provided gRPC endpoints + async fn connect(urls: &[String], chain_id: &str) -> Result { + let mut successful_connections = vec![]; + + for address in urls.iter() { + log::info!("Trying to connect to endpoint: {}", address); + + // try to connect to rpc endpoint + let maybe_client = HttpClient::new(address.as_str()); + + // connection succeeded or try the next rpc endpoint + let client = if maybe_client.is_ok() { + maybe_client? + } else { + continue; + }; + + // get client information for verification down below + let node_info = client.status().await?.node_info; + + // verify we are connected to the spected network + if node_info.network.as_str().ne(chain_id) { + log::error!( + "Network mismatch: connection:{} != config:{}", + node_info.network, + chain_id + ); + continue; + } + + // add endpoint to succesful connections + successful_connections.push(client) + } + + // we could not get any succesful connections + if successful_connections.is_empty() { + return Err(DaemonError::CannotConnectRPC); + } + + Ok(successful_connections.pop().unwrap()) + } + } + + #[cfg(test)] + mod tests { + /* + This test asserts breaking issues around the GRPC connection + */ + + use crate::DaemonAsync; + use speculoos::prelude::*; + + #[tokio::test] + async fn no_connection() { + let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; + let grpcs = &vec!["https://127.0.0.1:99999"]; + chain.grpc_urls = grpcs; + + let build_res = DaemonAsync::builder(chain) + .deployment_id("v0.1.0") + .build() + .await; + + asserting!("there is no GRPC connection") + .that(&build_res.err().unwrap().to_string()) + .is_equal_to(String::from( + "Can not connect to any grpc endpoint that was provided.", + )) + } + + #[tokio::test] + async fn network_grpcs_list_is_empty() { + let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; + let grpcs: &Vec<&str> = &vec![]; + chain.grpc_urls = grpcs; + + let build_res = DaemonAsync::builder(chain) + .deployment_id("v0.1.0") + .build() + .await; + + asserting!("GRPC list is empty") + .that(&build_res.err().unwrap().to_string()) + .is_equal_to(String::from("The list of grpc endpoints is empty")) + } + } +} + +mod grpc { + use cosmrs::proto::cosmos::base::tendermint::v1beta1::{ + service_client::ServiceClient, GetNodeInfoRequest, + }; + use cw_orch_core::{environment::ChainInfoOwned, log::connectivity_target}; + use tonic::transport::{Channel, ClientTlsConfig}; + + use crate::error::DaemonError; + + use super::Connect; + + /// A helper for constructing a gRPC channel + pub struct GrpcChannel {} + + impl Connect for GrpcChannel { + type Connection = Channel; + + /// Connect to any of the provided gRPC endpoints + async fn connect(grpc: &[String], chain_id: &str) -> Result { + if grpc.is_empty() { + return Err(DaemonError::GRPCListIsEmpty); + } + + let mut successful_connections = vec![]; + + for address in grpc.iter() { + log::debug!(target: &connectivity_target(), "Trying to connect to endpoint: {}", address); + + // get grpc endpoint + let endpoint = Channel::builder(address.clone().try_into().unwrap()); + + // try to connect to grpc endpoint + let maybe_client = ServiceClient::connect(endpoint.clone()).await; + + // connection succeeded + let mut client = if maybe_client.is_ok() { + maybe_client? + } else { + log::warn!( + "Cannot connect to gRPC endpoint: {}, {:?}", + address, + maybe_client.unwrap_err() + ); + + // try HTTPS approach + // https://github.com/hyperium/tonic/issues/363#issuecomment-638545965 + if !(address.contains("https") || address.contains("443")) { + continue; + }; + + log::debug!(target: &connectivity_target(), "Attempting to connect with TLS"); + + // re attempt to connect + let endpoint = endpoint.clone().tls_config(ClientTlsConfig::new())?; + let maybe_client = ServiceClient::connect(endpoint.clone()).await; + + // connection still fails + if maybe_client.is_err() { + log::warn!( + "Cannot connect to gRPC endpoint: {}, {:?}", + address, + maybe_client.unwrap_err() + ); + continue; + }; + + maybe_client? + }; + + // get client information for verification down below + let node_info = client + .get_node_info(GetNodeInfoRequest {}) + .await? + .into_inner(); + + // local juno does not return a proper ChainId with epoch format + // verify we are connected to the expected network + if node_info.default_node_info.as_ref().unwrap().network != chain_id { + log::error!( + "Network mismatch: connection:{} != config:{}", + node_info.default_node_info.as_ref().unwrap().network, + chain_id + ); + continue; + } + + // add endpoint to succesful connections + successful_connections.push(endpoint.connect().await?) + } + + // we could not get any succesful connections + if successful_connections.is_empty() { + return Err(DaemonError::CannotConnectGRPC); + } + + Ok(successful_connections.pop().unwrap()) + } + } + + #[cfg(test)] + mod tests { + /* + This test asserts breaking issues around the GRPC connection + */ + + use crate::DaemonAsync; + use speculoos::prelude::*; + + #[tokio::test] + #[serial_test::serial] + async fn no_connection() { + let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; + let grpcs = &["https://127.0.0.1:99999"]; + chain.grpc_urls = grpcs; + + let build_res = DaemonAsync::builder(chain) + .deployment_id("v0.1.0") + .build_sender(()) + .await; + + asserting!("there is no GRPC connection") + .that(&build_res.err().unwrap().to_string()) + .is_equal_to(String::from( + "Can not connect to any grpc endpoint that was provided.", + )) + } + + #[tokio::test] + #[serial_test::serial] + async fn network_grpcs_list_is_empty() { + let mut chain = cw_orch_daemon::networks::LOCAL_JUNO; + let grpcs = &[]; + chain.grpc_urls = grpcs; + + let build_res = DaemonAsync::builder(chain) + .deployment_id("v0.1.0") + .build_sender(()) + .await; + + asserting!("GRPC list is empty") + .that(&build_res.err().unwrap().to_string()) + .is_equal_to(String::from("The list of grpc endpoints is empty")) + } + } +} diff --git a/cw-orch-daemon/src/senders/cosmos.rs b/cw-orch-daemon/src/senders/cosmos.rs index d1aa639b2..d84b5e846 100644 --- a/cw-orch-daemon/src/senders/cosmos.rs +++ b/cw-orch-daemon/src/senders/cosmos.rs @@ -6,7 +6,7 @@ use crate::{ account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy, TxBroadcaster, }, - CosmosOptions, GrpcChannel, + CosmosOptions, }; use crate::proto::injective::InjectiveEthAccount; @@ -43,7 +43,9 @@ use std::{str::FromStr, sync::Arc}; use cosmos_modules::vesting::PeriodicVestingAccount; use tonic::transport::Channel; -use super::{cosmos_options::CosmosWalletKey, query::QuerySender, tx::TxSender}; +use super::{ + client::CosmosClient, cosmos_options::CosmosWalletKey, query::QuerySender, tx::TxSender, +}; const GAS_BUFFER: f64 = 1.3; const BUFFER_THRESHOLD: u64 = 200_000; @@ -57,8 +59,7 @@ pub type Wallet = CosmosSender; #[derive(Clone)] pub struct CosmosSender { pub private_key: PrivateKey, - /// gRPC channel - pub grpc_channel: Channel, + pub client: CosmosClient, /// Information about the chain pub chain_info: Arc, pub(crate) options: CosmosOptions, @@ -105,7 +106,7 @@ impl Wallet { Ok(Self { chain_info: chain_info.clone(), - grpc_channel: GrpcChannel::from_chain_info(chain_info.as_ref()).await?, + client: CosmosClient::from_chain_info(chain_info.as_ref()).await?, private_key: pk, secp, options, diff --git a/cw-orch-daemon/src/senders/mod.rs b/cw-orch-daemon/src/senders/mod.rs index e637e4470..710fb1de9 100644 --- a/cw-orch-daemon/src/senders/mod.rs +++ b/cw-orch-daemon/src/senders/mod.rs @@ -4,6 +4,7 @@ pub mod query; pub mod tx; // Senders +mod client; mod cosmos; mod cosmos_batch; mod cosmos_options; @@ -14,4 +15,5 @@ pub use { cosmos_batch::{options::CosmosBatchOptions, BatchDaemon, CosmosBatchSender}, cosmos_options::{CosmosOptions, CosmosWalletKey}, query_only::{QueryOnlyDaemon, QueryOnlySender}, + client::CosmosClient, }; diff --git a/cw-orch-daemon/src/senders/query_only.rs b/cw-orch-daemon/src/senders/query_only.rs index 1f30ede1d..58d37d8ec 100644 --- a/cw-orch-daemon/src/senders/query_only.rs +++ b/cw-orch-daemon/src/senders/query_only.rs @@ -6,7 +6,7 @@ use cw_orch_core::environment::ChainInfoOwned; use tonic::transport::Channel; -use super::{builder::SenderBuilder, query::QuerySender}; +use super::{builder::SenderBuilder, client::CosmosClient, query::QuerySender}; /// Daemon that does not support signing. /// Will err on any attempt to sign a transaction or retrieve a sender address. @@ -15,8 +15,8 @@ pub type QueryOnlyDaemon = DaemonBase; /// Signer of the transactions and helper for address derivation #[derive(Clone)] pub struct QueryOnlySender { - /// gRPC channel - pub channel: Channel, + /// CosmosSDK Client + pub client: CosmosClient, /// Information about the chain pub chain_info: Arc, } @@ -26,10 +26,10 @@ impl SenderBuilder for () { type Sender = QueryOnlySender; async fn build(&self, chain_info: &Arc) -> Result { - let channel = GrpcChannel::from_chain_info(chain_info.as_ref()).await?; + let client = CosmosClient::from_chain_info(chain_info.as_ref()).await?; Ok(QueryOnlySender { - channel, + client, chain_info: chain_info.clone(), }) } diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index 876cb54c8..25961b9ca 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -1,5 +1,11 @@ use super::error::DaemonError; +use crate::networks::ChainKind; + use crate::env::{default_state_folder, DaemonEnvVars}; +#[cfg(feature = "grpc")] +use crate::grpc_channel::GrpcChannel; +#[cfg(feature = "rpc")] +use crate::rpc_channel::RpcChannel; use crate::{json_lock::JsonLockedState, networks::ChainKind}; use cosmwasm_std::Addr; @@ -9,6 +15,7 @@ use once_cell::sync::Lazy; use serde::Serialize; use serde_json::{json, Value}; use std::sync::Arc; +use std::{collections::HashMap, env, fs::File, path::Path}; use std::{ collections::{HashMap, HashSet}, path::Path, @@ -53,6 +60,20 @@ pub enum DaemonStateFile { }, } +#[cfg(feature = "grpc")] +pub async fn create_transport_channel( + chain_data: &ChainData, +) -> Result { + GrpcChannel::connect(&chain_data.apis.grpc, &chain_data.chain_id).await +} + +#[cfg(feature = "rpc")] +pub async fn create_transport_channel( + chain_data: &ChainData, +) -> Result { + RpcChannel::connect(&chain_data.apis.rpc, &chain_data.chain_id).await +} + impl DaemonState { /// Creates a new state from the given chain data and deployment id. /// Attempts to connect to any of the provided gRPC endpoints. diff --git a/cw-orch-daemon/src/sync/core.rs b/cw-orch-daemon/src/sync/core.rs index 8e64cc8d1..76d770539 100644 --- a/cw-orch-daemon/src/sync/core.rs +++ b/cw-orch-daemon/src/sync/core.rs @@ -14,7 +14,6 @@ use cw_orch_core::{ use cw_orch_traits::stargate::Stargate; use serde::Serialize; use tokio::runtime::Handle; -use tonic::transport::Channel; use crate::senders::tx::TxSender; diff --git a/cw-orch-daemon/src/tx_builder.rs b/cw-orch-daemon/src/tx_builder.rs index 4aeee68a7..d7ca6026c 100644 --- a/cw-orch-daemon/src/tx_builder.rs +++ b/cw-orch-daemon/src/tx_builder.rs @@ -100,6 +100,8 @@ impl TxBuilder { .. } = wallet.base_account().await?; + log::trace!("Retrieved base account"); + // overwrite sequence if set (can be used for concurrent txs) let sequence = self.sequence.unwrap_or(sequence); @@ -115,6 +117,7 @@ impl TxBuilder { ); (fee, gas_limit) } else { + log::debug!("Calculating new fee and gas limits"); let sim_gas_used = wallet .calculate_gas(&self.body, sequence, account_number) .await?; diff --git a/cw-orch-daemon/src/tx_resp.rs b/cw-orch-daemon/src/tx_resp.rs index 8c69006d9..c6fb27fa0 100644 --- a/cw-orch-daemon/src/tx_resp.rs +++ b/cw-orch-daemon/src/tx_resp.rs @@ -1,9 +1,10 @@ +use cosmrs::rpc::endpoint; use prost::bytes::Bytes; use super::{ cosmos_modules::{ abci::{AbciMessageLog, Attribute, StringEvent, TxResponse}, - tendermint_abci::Event, + tendermint_abci::{Event, EventAttribute}, }, error::DaemonError, }; @@ -157,7 +158,63 @@ impl From for CosmTxResponse { gas_wanted: tx.gas_wanted as u64, gas_used: tx.gas_used as u64, timestamp: parse_timestamp(tx.timestamp).unwrap(), - events: tx.events, + events: tx + .events + .into_iter() + .map(|e| Event { + r#type: e.r#type, + attributes: e + .attributes + .into_iter() + .map(|a| EventAttribute { + key: String::from_utf8(a.key.clone().to_vec()).unwrap(), + value: String::from_utf8(a.value.clone().to_vec()).unwrap(), + index: false, + }) + .collect(), + }) + .collect(), + } + } +} + +impl From for CosmTxResponse { + fn from(tx: endpoint::tx::Response) -> Self { + Self { + height: tx.height.value(), + txhash: match tx.hash { + Hash::None => "".to_string(), + Hash::Sha256(x) => hex::encode(x), + }, + codespace: tx.tx_result.codespace, + code: match tx.tx_result.code { + Code::Ok => 0, + Code::Err(code) => code.get() as usize, + }, + data: String::from_utf8(tx.tx_result.data.clone().to_vec()).unwrap(), + raw_log: tx.tx_result.log, + logs: vec![], + info: tx.tx_result.info, + gas_wanted: tx.tx_result.gas_wanted as u64, + gas_used: tx.tx_result.gas_used as u64, + timestamp: DateTime::default(), + events: tx + .tx_result + .events + .into_iter() + .map(|e| Event { + r#type: e.kind, + attributes: e + .attributes + .into_iter() + .map(|a| EventAttribute { + key: a.key, + value: a.value, + index: false, + }) + .collect(), + }) + .collect(), } } } diff --git a/cw-orch-daemon/tests/querier.rs b/cw-orch-daemon/tests/querier.rs index 4c917950f..84f0b4a1e 100644 --- a/cw-orch-daemon/tests/querier.rs +++ b/cw-orch-daemon/tests/querier.rs @@ -23,12 +23,39 @@ mod queriers { AccountId, Denom, }; + #[cfg(feature = "grpc")] pub async fn build_channel() -> tonic::transport::Channel { + use ibc_chain_registry::chain::Grpc; + let network = networks::LOCAL_JUNO; let grpcs = vec![network.grpc_urls[0].into()]; - let channel = GrpcChannel::connect(&grpcs, network.chain_id).await; + let chain: ChainId = ChainId::new(network.chain_id.to_owned(), 1); + + let channel = cw_orch_daemon::GrpcChannel::connect(&grpcs, &chain).await; + + asserting!("channel connection is succesful") + .that(&channel) + .is_ok(); + + channel.unwrap() + } + + #[cfg(feature = "rpc")] + pub async fn build_channel() -> cosmrs::rpc::HttpClient { + use ibc_chain_registry::chain::Rpc; + + let network = networks::LOCAL_JUNO; + + let rpcs: Vec = vec![Rpc { + address: network.rpc_urls[0].into(), + provider: None, + }]; + + let chain: ChainId = ChainId::new(network.chain_id.to_owned(), 1); + + let channel = cw_orch_daemon::RpcChannel::connect(&rpcs, &chain).await; asserting!("channel connection is succesful") .that(&channel) diff --git a/cw-orch-daemon/tests/rpc-querier.rs b/cw-orch-daemon/tests/rpc-querier.rs new file mode 100644 index 000000000..963e47677 --- /dev/null +++ b/cw-orch-daemon/tests/rpc-querier.rs @@ -0,0 +1,40 @@ +use base64::{engine::general_purpose, Engine as _}; +use cosmrs::{ + proto::cosmos::bank::v1beta1::{QueryAllBalancesRequest, QueryAllBalancesResponse}, + rpc::{endpoint::abci_query, Client, HttpClient}, + tx::MessageExt, +}; +use hex::encode; +use prost::Message; +use tokio::runtime::Runtime; + +#[test] +fn temp_test() { + // Necessary variable for querying + let rt = Runtime::new().unwrap(); + let client = HttpClient::new("https://rpc.osmosis.zone").unwrap(); + + // Query content + let address = "osmo126pr9qp44aft4juw7x4ev4s2qdtnwe38jzwunec9pxt5cpzaaphqyagqpu".to_string(); + let request = QueryAllBalancesRequest { + address, + pagination: None, + }; + let any = request.to_bytes().unwrap(); + + // Querying + let response = rt + .block_on(client.abci_query( + Some("/cosmos.bank.v1beta1.Query/AllBalances".to_string()), + any, + None, + true, + )) + .unwrap(); + + // Analysing the response + let balance_response = QueryAllBalancesResponse::decode(response.value.as_slice()).unwrap(); + + // Printing the response + panic!("{:?}", balance_response); +} diff --git a/packages/cw-orch-core/src/environment/chain_info.rs b/packages/cw-orch-core/src/environment/chain_info.rs index 5b86c75ec..ccf422fbf 100644 --- a/packages/cw-orch-core/src/environment/chain_info.rs +++ b/packages/cw-orch-core/src/environment/chain_info.rs @@ -21,6 +21,8 @@ pub struct ChainInfoBase, StringArrayType: AsRef<[Strin pub gas_price: f64, /// gRPC urls, used to attempt connection pub grpc_urls: StringArrayType, + /// RPC urls, used to attempt connection + pub rpc_urls: StringArrayType, /// Optional urls for custom functionality pub lcd_url: Option, /// Optional urls for custom functionality @@ -49,6 +51,7 @@ impl From for ChainInfoOwned { gas_denom: value.gas_denom.to_string(), gas_price: value.gas_price, grpc_urls: value.grpc_urls.iter().map(|url| url.to_string()).collect(), + rpc_urls: value.rpc_urls.iter().map(|url| url.to_string()).collect(), lcd_url: value.lcd_url.map(ToString::to_string), fcd_url: value.fcd_url.map(ToString::to_string), network_info: value.network_info.into(), diff --git a/packages/cw-orch-networks/src/lib.rs b/packages/cw-orch-networks/src/lib.rs index 4fda3fee8..f52c972f1 100644 --- a/packages/cw-orch-networks/src/lib.rs +++ b/packages/cw-orch-networks/src/lib.rs @@ -1 +1,23 @@ pub mod networks; + +pub use cw_orch_core::environment::{ChainInfo, ChainKind, NetworkInfo}; + +use networks::SUPPORTED_NETWORKS; + +/// A helper function to retrieve a [`ChainInfo`] struct for a given chain-id. +/// +/// ## Example +/// ```rust,no_run +/// use cw_orch_networks::networks::{parse_network, ChainInfo}; +/// +/// let juno_mainnet: ChainInfo = parse_network("juno-1").unwrap(); +/// ``` +/// --- +/// supported chains are defined by the `SUPPORT_NETWORKS` variable +pub fn parse_network(net_id: &str) -> Result { + SUPPORTED_NETWORKS + .iter() + .find(|net| net.chain_id == net_id) + .cloned() + .ok_or(format!("Network not found: {}", net_id)) +} diff --git a/packages/cw-orch-networks/src/networks/archway.rs b/packages/cw-orch-networks/src/networks/archway.rs index 8b54077c6..3d586dc25 100644 --- a/packages/cw-orch-networks/src/networks/archway.rs +++ b/packages/cw-orch-networks/src/networks/archway.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: archway pub const ARCHWAY_NETWORK: NetworkInfo = NetworkInfo { @@ -15,6 +15,7 @@ pub const CONSTANTINE_3: ChainInfo = ChainInfo { gas_denom: "aconst", gas_price: 1000000000000.0, grpc_urls: &["https://grpc.constantine.archway.io:443"], + rpc_urls: &[], network_info: ARCHWAY_NETWORK, lcd_url: Some("https://api.constantine.archway.io"), fcd_url: None, @@ -28,6 +29,7 @@ pub const ARCHWAY_1: ChainInfo = ChainInfo { gas_denom: "aarch", gas_price: 1000000000000.0, grpc_urls: &["https://grpc.mainnet.archway.io:443"], + rpc_urls: &["https://rpc.mainnet.archway.io"], network_info: ARCHWAY_NETWORK, lcd_url: Some("https://api.mainnet.archway.io"), fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/doravota.rs b/packages/cw-orch-networks/src/networks/doravota.rs index 5583a4867..3c9eab4df 100644 --- a/packages/cw-orch-networks/src/networks/doravota.rs +++ b/packages/cw-orch-networks/src/networks/doravota.rs @@ -14,6 +14,7 @@ pub const VOTA_ASH: ChainInfo = ChainInfo { chain_id: "vota-ash", gas_denom: "peaka", gas_price: 100000000000f64, + rpc_urls: &[], grpc_urls: &["https://vota-grpc.dorafactory.org:443"], network_info: DORAVOTA_NETWORK, lcd_url: None, @@ -25,6 +26,7 @@ pub const VOTA_TESTNET: ChainInfo = ChainInfo { chain_id: "vota-testnet", gas_denom: "peaka", gas_price: 100000000000f64, + rpc_urls: &[], grpc_urls: &["https://vota-testnet-grpc.dorafactory.org:443"], network_info: DORAVOTA_NETWORK, lcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/injective.rs b/packages/cw-orch-networks/src/networks/injective.rs index b61ca639f..80c9a3fde 100644 --- a/packages/cw-orch-networks/src/networks/injective.rs +++ b/packages/cw-orch-networks/src/networks/injective.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: injective pub const INJECTIVE_NETWORK: NetworkInfo = NetworkInfo { @@ -16,6 +16,7 @@ pub const INJECTIVE_1: ChainInfo = ChainInfo { gas_denom: "inj", gas_price: 500_000_000.0, grpc_urls: &["https://sentry.chain.grpc.injective.network:443"], + rpc_urls: &[], network_info: INJECTIVE_NETWORK, lcd_url: None, fcd_url: None, @@ -29,6 +30,7 @@ pub const INJECTIVE_888: ChainInfo = ChainInfo { gas_denom: "inj", gas_price: 500_000_000.0, grpc_urls: &["https://k8s.testnet.chain.grpc.injective.network:443"], + rpc_urls: &[], network_info: INJECTIVE_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/juno.rs b/packages/cw-orch-networks/src/networks/juno.rs index 06466a705..a3002678c 100644 --- a/packages/cw-orch-networks/src/networks/juno.rs +++ b/packages/cw-orch-networks/src/networks/juno.rs @@ -15,6 +15,7 @@ pub const UNI_6: ChainInfo = ChainInfo { gas_denom: "ujunox", gas_price: 0.025, grpc_urls: &["http://juno-testnet-grpc.polkachu.com:12690"], + rpc_urls: &[], network_info: JUNO_NETWORK, lcd_url: None, fcd_url: None, @@ -26,6 +27,7 @@ pub const JUNO_1: ChainInfo = ChainInfo { gas_denom: "ujuno", gas_price: 0.0750, grpc_urls: &["http://juno-grpc.polkachu.com:12690"], + rpc_urls: &[], network_info: JUNO_NETWORK, lcd_url: None, fcd_url: None, @@ -37,6 +39,7 @@ pub const LOCAL_JUNO: ChainInfo = ChainInfo { gas_denom: "ujunox", gas_price: 0.0, grpc_urls: &["http://localhost:9090"], + rpc_urls: &["http://localhost:26657"], network_info: JUNO_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/kujira.rs b/packages/cw-orch-networks/src/networks/kujira.rs index c040996b9..5b24bc71a 100644 --- a/packages/cw-orch-networks/src/networks/kujira.rs +++ b/packages/cw-orch-networks/src/networks/kujira.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: kujira pub const KUJIRA_NETWORK: NetworkInfo = NetworkInfo { @@ -13,6 +13,7 @@ pub const HARPOON_4: ChainInfo = ChainInfo { gas_denom: "ukuji", gas_price: 0.025, grpc_urls: &["http://kujira-testnet-grpc.polkachu.com:11890"], + rpc_urls: &[], network_info: KUJIRA_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/landslide.rs b/packages/cw-orch-networks/src/networks/landslide.rs index 7d05fb0d9..4002cbdd7 100644 --- a/packages/cw-orch-networks/src/networks/landslide.rs +++ b/packages/cw-orch-networks/src/networks/landslide.rs @@ -12,6 +12,7 @@ pub const LOCAL_LANDSLIDE: ChainInfo = ChainInfo { chain_id: "landslide-test", gas_denom: "stake", gas_price: 1_f64, + rpc_urls: &[], grpc_urls: &["http://127.0.0.1:9090"], network_info: LANDSLIDE_NETWORK, lcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/migaloo.rs b/packages/cw-orch-networks/src/networks/migaloo.rs index bacd45669..85f0be97e 100644 --- a/packages/cw-orch-networks/src/networks/migaloo.rs +++ b/packages/cw-orch-networks/src/networks/migaloo.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: migaloo pub const MIGALOO_NETWORK: NetworkInfo = NetworkInfo { @@ -13,6 +13,7 @@ pub const LOCAL_MIGALOO: ChainInfo = ChainInfo { gas_denom: "uwhale", gas_price: 0.1, grpc_urls: &["http://localhost:9090"], + rpc_urls: &[], network_info: MIGALOO_NETWORK, lcd_url: None, fcd_url: None, @@ -25,6 +26,7 @@ pub const NARWHAL_1: ChainInfo = ChainInfo { gas_denom: "uwhale", gas_price: 0.1, grpc_urls: &["migaloo-testnet-grpc.polkachu.com:20790"], + rpc_urls: &[], network_info: MIGALOO_NETWORK, lcd_url: None, fcd_url: None, @@ -37,6 +39,7 @@ pub const MIGALOO_1: ChainInfo = ChainInfo { gas_denom: "uwhale", gas_price: 0.1, grpc_urls: &["migaloo-grpc.polkachu.com:20790"], + rpc_urls: &[], network_info: MIGALOO_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/mod.rs b/packages/cw-orch-networks/src/networks/mod.rs index 066be1834..e84f292f2 100644 --- a/packages/cw-orch-networks/src/networks/mod.rs +++ b/packages/cw-orch-networks/src/networks/mod.rs @@ -15,10 +15,11 @@ pub mod osmosis; pub mod rollkit; pub mod sei; pub mod terra; +pub mod wasm; pub mod xion; pub use archway::{ARCHWAY_1, CONSTANTINE_3}; -pub use cw_orch_core::environment::{ChainInfo, ChainKind, NetworkInfo}; +use cw_orch_core::environment::ChainInfo; pub use doravota::{VOTA_ASH, VOTA_TESTNET}; pub use injective::{INJECTIVE_1, INJECTIVE_888}; pub use juno::{JUNO_1, LOCAL_JUNO, UNI_6}; @@ -31,24 +32,8 @@ pub use osmosis::{LOCAL_OSMO, OSMOSIS_1, OSMO_5}; pub use rollkit::{LOCAL_ROLLKIT, ROLLKIT_TESTNET}; pub use sei::{ATLANTIC_2, LOCAL_SEI, PACIFIC_1, SEI_DEVNET_3}; pub use terra::{LOCAL_TERRA, PHOENIX_1, PISCO_1}; +pub use wasm::LOCAL_WASMD; pub use xion::XION_TESTNET_1; -/// A helper function to retrieve a [`ChainInfo`] struct for a given chain-id. -/// -/// ## Example -/// ```rust,no_run -/// use cw_orch_networks::networks::{parse_network, ChainInfo}; -/// let juno_mainnet: ChainInfo = parse_network("juno-1").unwrap(); -/// ``` -/// --- -/// supported chains are defined by the `SUPPORT_NETWORKS` variable - -pub fn parse_network(net_id: &str) -> Result { - SUPPORTED_NETWORKS - .iter() - .find(|net| net.chain_id == net_id) - .cloned() - .ok_or(format!("Network not found: {}", net_id)) -} pub const SUPPORTED_NETWORKS: &[ChainInfo] = &[ UNI_6, diff --git a/packages/cw-orch-networks/src/networks/neutron.rs b/packages/cw-orch-networks/src/networks/neutron.rs index 0af2a2b23..a5ad5bbef 100644 --- a/packages/cw-orch-networks/src/networks/neutron.rs +++ b/packages/cw-orch-networks/src/networks/neutron.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: neutron pub const NEUTRON_NETWORK: NetworkInfo = NetworkInfo { @@ -14,6 +14,7 @@ pub const PION_1: ChainInfo = ChainInfo { gas_denom: "untrn", gas_price: 0.075, grpc_urls: &["http://grpc-palvus.pion-1.ntrn.tech:80"], + rpc_urls: &[], network_info: NEUTRON_NETWORK, lcd_url: Some("https://rest-palvus.pion-1.ntrn.tech"), fcd_url: None, @@ -26,6 +27,7 @@ pub const NEUTRON_1: ChainInfo = ChainInfo { gas_denom: "untrn", gas_price: 0.075, grpc_urls: &["http://grpc-kralum.neutron-1.neutron.org:80"], + rpc_urls: &[], network_info: NEUTRON_NETWORK, lcd_url: Some("https://rest-kralum.neutron-1.neutron.org"), fcd_url: None, @@ -37,6 +39,7 @@ pub const LOCAL_NEUTRON: ChainInfo = ChainInfo { gas_denom: "untrn", gas_price: 0.0025, grpc_urls: &["http://localhost:8090"], + rpc_urls: &[], network_info: NEUTRON_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/nibiru.rs b/packages/cw-orch-networks/src/networks/nibiru.rs index 9ec1bd43d..0d79af930 100644 --- a/packages/cw-orch-networks/src/networks/nibiru.rs +++ b/packages/cw-orch-networks/src/networks/nibiru.rs @@ -12,6 +12,7 @@ pub const NIBIRU_ITN_2: ChainInfo = ChainInfo { chain_id: "nibiru-itn-2", gas_denom: "unibi", gas_price: 0.025, + rpc_urls: &[], grpc_urls: &["https://nibiru-testnet.grpc.kjnodes.com:443"], network_info: NIBIRU_NETWORK, lcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/osmosis.rs b/packages/cw-orch-networks/src/networks/osmosis.rs index f5e018c5b..d48551161 100644 --- a/packages/cw-orch-networks/src/networks/osmosis.rs +++ b/packages/cw-orch-networks/src/networks/osmosis.rs @@ -13,6 +13,7 @@ pub const OSMOSIS_1: ChainInfo = ChainInfo { gas_denom: "uosmo", gas_price: 0.025, grpc_urls: &["https://grpc.osmosis.zone:443"], + rpc_urls: &[], network_info: OSMO_NETWORK, lcd_url: None, fcd_url: None, @@ -24,6 +25,7 @@ pub const OSMO_5: ChainInfo = ChainInfo { gas_denom: "uosmo", gas_price: 0.025, grpc_urls: &["https://grpc.osmotest5.osmosis.zone:443"], + rpc_urls: &[], network_info: OSMO_NETWORK, lcd_url: None, fcd_url: None, @@ -35,6 +37,7 @@ pub const LOCAL_OSMO: ChainInfo = ChainInfo { gas_denom: "uosmo", gas_price: 0.0026, grpc_urls: &["http://65.108.235.46:9094"], + rpc_urls: &[], network_info: OSMO_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/rollkit.rs b/packages/cw-orch-networks/src/networks/rollkit.rs index 994f6bd49..2b6dae525 100644 --- a/packages/cw-orch-networks/src/networks/rollkit.rs +++ b/packages/cw-orch-networks/src/networks/rollkit.rs @@ -12,6 +12,7 @@ pub const LOCAL_ROLLKIT: ChainInfo = ChainInfo { chain_id: "celeswasm", gas_denom: "uwasm", gas_price: 0.025, + rpc_urls: &[], grpc_urls: &["http://localhost:9290"], network_info: ROLLKIT_NETWORK, lcd_url: None, @@ -23,6 +24,7 @@ pub const ROLLKIT_TESTNET: ChainInfo = ChainInfo { chain_id: "rosm", gas_denom: "urosm", gas_price: 0.025, + rpc_urls: &[], grpc_urls: &["http://grpc.rosm.rollkit.dev:9290"], network_info: ROLLKIT_NETWORK, lcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/sei.rs b/packages/cw-orch-networks/src/networks/sei.rs index c955b1137..40df8d323 100644 --- a/packages/cw-orch-networks/src/networks/sei.rs +++ b/packages/cw-orch-networks/src/networks/sei.rs @@ -1,4 +1,4 @@ -use crate::networks::{ChainInfo, ChainKind, NetworkInfo}; +use crate::{ChainInfo, ChainKind, NetworkInfo}; // ANCHOR: sei pub const SEI_NETWORK: NetworkInfo = NetworkInfo { @@ -13,6 +13,7 @@ pub const LOCAL_SEI: ChainInfo = ChainInfo { gas_denom: "usei", gas_price: 0.1, grpc_urls: &["http://localhost:9090"], + rpc_urls: &[], network_info: SEI_NETWORK, lcd_url: None, fcd_url: None, @@ -24,6 +25,7 @@ pub const SEI_DEVNET_3: ChainInfo = ChainInfo { gas_denom: "usei", gas_price: 0.1, grpc_urls: &["http://sei_devnet-testnet-grpc.polkachu.com:11990"], + rpc_urls: &[], network_info: SEI_NETWORK, lcd_url: None, fcd_url: None, @@ -35,6 +37,7 @@ pub const ATLANTIC_2: ChainInfo = ChainInfo { gas_denom: "usei", gas_price: 0.1, grpc_urls: &["http://sei-testnet-grpc.polkachu.com:11990"], + rpc_urls: &[], network_info: SEI_NETWORK, lcd_url: None, fcd_url: None, @@ -45,6 +48,7 @@ pub const PACIFIC_1: ChainInfo = ChainInfo { chain_id: "pacific-1", gas_denom: "usei", gas_price: 0.1, + rpc_urls: &[], grpc_urls: &["http://sei-grpc.polkachu.com:11990"], network_info: SEI_NETWORK, lcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/terra.rs b/packages/cw-orch-networks/src/networks/terra.rs index f220b7310..6711680a4 100644 --- a/packages/cw-orch-networks/src/networks/terra.rs +++ b/packages/cw-orch-networks/src/networks/terra.rs @@ -15,6 +15,7 @@ pub const PISCO_1: ChainInfo = ChainInfo { gas_denom: "uluna", gas_price: 0.015, grpc_urls: &["http://terra-testnet-grpc.polkachu.com:11790"], + rpc_urls: &[], network_info: TERRA_NETWORK, lcd_url: None, fcd_url: None, @@ -28,6 +29,7 @@ pub const PHOENIX_1: ChainInfo = ChainInfo { gas_denom: "uluna", gas_price: 0.015, grpc_urls: &["http://terra-grpc.polkachu.com:11790"], + rpc_urls: &[], network_info: TERRA_NETWORK, lcd_url: None, fcd_url: None, @@ -41,6 +43,7 @@ pub const LOCAL_TERRA: ChainInfo = ChainInfo { gas_denom: "uluna", gas_price: 0.15, grpc_urls: &["http://localhost:9090"], + rpc_urls: &[], network_info: TERRA_NETWORK, lcd_url: None, fcd_url: None, diff --git a/packages/cw-orch-networks/src/networks/wasm.rs b/packages/cw-orch-networks/src/networks/wasm.rs new file mode 100644 index 000000000..71482e600 --- /dev/null +++ b/packages/cw-orch-networks/src/networks/wasm.rs @@ -0,0 +1,22 @@ +use crate::{ChainInfo, ChainKind, NetworkInfo}; + +// ANCHOR: wasmd +/// Wasmd local network +pub const WASMD_NETWORK: NetworkInfo = NetworkInfo { + chain_name: "wasm", + pub_address_prefix: "wasm", + coin_type: 118u32, +}; + +pub const LOCAL_WASMD: ChainInfo = ChainInfo { + kind: ChainKind::Local, + chain_id: "testing", + gas_denom: "ucosm", + gas_price: 0.1, + grpc_urls: &["http://localhost:9090"], + rpc_urls: &["http://localhost:26657"], + network_info: WASMD_NETWORK, + lcd_url: None, + fcd_url: None, +}; +// ANCHOR_END: wasmd diff --git a/packages/cw-orch-networks/src/networks/xion.rs b/packages/cw-orch-networks/src/networks/xion.rs index fe5f6a608..b06efb1b2 100644 --- a/packages/cw-orch-networks/src/networks/xion.rs +++ b/packages/cw-orch-networks/src/networks/xion.rs @@ -12,6 +12,7 @@ pub const XION_TESTNET_1: ChainInfo = ChainInfo { chain_id: "xion-testnet-1", gas_denom: "uxion", gas_price: 0.0, + rpc_urls: &[], grpc_urls: &["http://xion-testnet-grpc.polkachu.com:22390"], network_info: XION_NETWORK, lcd_url: None,