diff --git a/contracts/counter/examples/async.rs b/contracts/counter/examples/async.rs index 2c1fc7dd1..320b595c8 100644 --- a/contracts/counter/examples/async.rs +++ b/contracts/counter/examples/async.rs @@ -13,7 +13,7 @@ pub async fn main() -> anyhow::Result<()> { pretty_env_logger::init(); // Used to log contract and chain interactions let network = networks::LOCAL_JUNO; - let chain = DaemonAsyncBuilder::default().chain(network).build().await?; + let chain = DaemonAsyncBuilder::new(network).build().await?; let counter = CounterContract::new(chain); diff --git a/contracts/counter/examples/deploy.rs b/contracts/counter/examples/deploy.rs index df07750ee..2ba8a43ef 100644 --- a/contracts/counter/examples/deploy.rs +++ b/contracts/counter/examples/deploy.rs @@ -13,7 +13,7 @@ pub fn main() -> anyhow::Result<()> { pretty_env_logger::init(); // Used to log contract and chain interactions let network = networks::LOCAL_JUNO; - let chain = DaemonBuilder::default().chain(network).build()?; + let chain = DaemonBuilder::new(network).build()?; // ANCHOR_END: chain_construction // ANCHOR: contract_interaction diff --git a/contracts/counter/tests/osmosis-test-tube.rs b/contracts/counter/tests/osmosis-test-tube.rs index cfac902db..4fefcf84c 100644 --- a/contracts/counter/tests/osmosis-test-tube.rs +++ b/contracts/counter/tests/osmosis-test-tube.rs @@ -28,7 +28,7 @@ fn setup(chain: Chain) -> CounterContract { // Instantiate the contract let msg = InstantiateMsg { count: 1i32 }; let init_resp = contract - .instantiate(&msg, Some(&chain.sender()), None) + .instantiate(&msg, Some(&chain.sender_addr()), None) .unwrap(); // Get the address from the response diff --git a/cw-orch-daemon/Cargo.toml b/cw-orch-daemon/Cargo.toml index c5525251c..98fdc7d71 100644 --- a/cw-orch-daemon/Cargo.toml +++ b/cw-orch-daemon/Cargo.toml @@ -56,7 +56,7 @@ chrono = { version = "0.4" } base16 = { version = "0.2.1" } ring = { version = "0.17.3" } dirs = "5.0.1" - +tower = { version = "0.4", features = ["retry", "reconnect"] } # Injective dependencies ethers-signers = { version = "2.0.7", optional = true } @@ -66,6 +66,8 @@ async-recursion = "1.0.5" # Gzip flate2 = { version = "1.0.26" } lazy_static = "1.4.0" +http = "0.2.11" +hyper = "0.14.27" # Lock daemon file-lock = { version = "2.1.10" } @@ -74,6 +76,7 @@ regex = "1.10.4" [dev-dependencies] cw-orch-daemon = { path = "." } +cw-orch = { path = "../cw-orch", features = ["daemon"] } uid = "0.1.7" env_logger = "0.11.2" cw20 = { version = "1" } @@ -94,3 +97,6 @@ tokio-test = "0.4.3" # File lock test nix = { version = "0.28.0", features = ["process"] } +counter-contract = { path = "../contracts/counter" } +dotenv = "0.15.0" +pretty_env_logger = "0.5.0" diff --git a/cw-orch-daemon/examples/batch-sender.rs b/cw-orch-daemon/examples/batch-sender.rs new file mode 100644 index 000000000..fedbbb444 --- /dev/null +++ b/cw-orch-daemon/examples/batch-sender.rs @@ -0,0 +1,49 @@ +// ANCHOR: full_counter_example +use counter_contract::{ + msg::InstantiateMsg, CounterContract, CounterExecuteMsgFns, CounterQueryMsgFns, +}; +use cw_orch::{anyhow, daemon::senders::BatchDaemon, prelude::*}; +use cw_orch_daemon::senders::CosmosBatchOptions; + +// From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L2 +const LOCAL_MNEMONIC: &str = "clip hire initial neck maid actor venue client foam budget lock catalog sweet steak waste crater broccoli pipe steak sister coyote moment obvious choose"; +pub fn main() -> anyhow::Result<()> { + std::env::set_var("LOCAL_MNEMONIC", LOCAL_MNEMONIC); + dotenv::dotenv().ok(); // Used to load the `.env` file if any + pretty_env_logger::init(); // Used to log contract and chain interactions + + let network = networks::LOCAL_JUNO; + let chain: BatchDaemon = + BatchDaemon::builder(network).build_sender(CosmosBatchOptions::default())?; + + let counter = CounterContract::new(chain.clone()); + + counter.upload()?; + counter.instantiate(&InstantiateMsg { count: 0 }, None, None)?; + + counter.increment()?; + + // The count hasn't been incremented yet, we didn't broadcast the tx + let count = counter.get_count()?; + assert_eq!(count.count, 0); + + chain.rt_handle.block_on(chain.sender().broadcast(None))?; + + let count = counter.get_count()?; + assert_eq!(count.count, 1); + + // Increment multiple times in the same transaction + counter.increment()?; + counter.increment()?; + counter.increment()?; + counter.increment()?; + counter.increment()?; + counter.increment()?; + + chain.rt_handle.block_on(chain.sender().broadcast(None))?; + + let count = counter.get_count()?; + assert_eq!(count.count, 7); + + Ok(()) +} diff --git a/cw-orch-daemon/examples/daemon-capabilities.rs b/cw-orch-daemon/examples/daemon-capabilities.rs index 4a40b2094..9f3a2b6a5 100644 --- a/cw-orch-daemon/examples/daemon-capabilities.rs +++ b/cw-orch-daemon/examples/daemon-capabilities.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use cosmrs::{tx::Msg, AccountId, Coin, Denom}; use cosmwasm_std::coins; // ANCHOR: full_counter_example +use cw_orch_daemon::senders::tx::TxSender; use cw_orch_daemon::DaemonBuilder; use cw_orch_networks::networks; @@ -12,13 +13,14 @@ pub fn main() -> anyhow::Result<()> { std::env::set_var("LOCAL_MNEMONIC", LOCAL_MNEMONIC); let network = networks::LOCAL_JUNO; - let mut daemon = DaemonBuilder::default().chain(network).build()?; + let mut daemon = DaemonBuilder::new(network).build()?; daemon.flush_state()?; // We commit the tx (also resimulates the tx) // ANCHOR: send_tx - let wallet = daemon.wallet(); + let wallet = daemon.sender(); + let rt = daemon.rt_handle.clone(); rt.block_on(wallet.bank_send("", coins(345, "ujunox")))?; // ANCHOR_END: send_tx diff --git a/cw-orch-daemon/examples/manual_sender.rs b/cw-orch-daemon/examples/manual_sender.rs new file mode 100644 index 000000000..9c9b4fdc4 --- /dev/null +++ b/cw-orch-daemon/examples/manual_sender.rs @@ -0,0 +1,196 @@ +// This file illustrates an example for custom sender inside Daemon + +use cw_orch_daemon::proto::injective::InjectiveEthAccount; +use cw_orch_daemon::queriers::Node; +use cw_orch_daemon::senders::builder::SenderBuilder; +use cw_orch_daemon::senders::query::QuerySender; +use cw_orch_daemon::tx_broadcaster::assert_broadcast_code_cosm_response; +use cw_orch_daemon::{DaemonBase, GrpcChannel, TxBuilder}; + +use cw_orch_daemon::{CosmTxResponse, DaemonError}; + +use cosmrs::proto::cosmos; +use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount; +use cosmrs::proto::cosmos::vesting::v1beta1::PeriodicVestingAccount; +use cosmrs::tendermint::chain::Id; +use cosmrs::tx::{ModeInfo, Raw, SignDoc, SignMode, SignerInfo}; +use cosmrs::{AccountId, Any}; +use cosmwasm_std::Addr; +use cw_orch::prelude::*; +use cw_orch_core::environment::ChainInfoOwned; +use prost::Message; +use std::io::{self, Write}; +use std::sync::Arc; +use tonic::transport::Channel; + +// ANCHOR: full_counter_example +use counter_contract::CounterContract; + +// This is a test with a manual sender, to verify everything works, nothing is broadcasted + +pub fn main() -> anyhow::Result<()> { + dotenv::dotenv().ok(); // Used to load the `.env` file if any + pretty_env_logger::init(); // Used to log contract and chain interactions + + let network = cw_orch_networks::networks::JUNO_1; + let sender = "juno1xjf5xscdk08c5es2m7epmerrpqmkmc3n98650t"; + let chain: ManualDaemon = ManualDaemon::builder(network).build_sender(ManualSenderOptions { + sender_address: sender.to_string(), + })?; + + let counter = CounterContract::new(chain.clone()); + + // Example tx hash that succeed (correspond to a code upload tx) + // 58AA802705BEE4597A560FBC67F6C86400E66F5FCBD0F08AA37FB140BCD65B6D + // If not found, try to find the latests juno code uploaded (4380+) + // https://www.mintscan.io/juno/wasm/code/4380 + counter.upload()?; + + Ok(()) +} + +use cw_orch_daemon::senders::tx::TxSender; + +pub type ManualDaemon = DaemonBase; + +#[derive(Clone, Default)] +pub struct ManualSenderOptions { + pub sender_address: String, +} + +/// Signer of the transactions and helper for address derivation +/// This is the main interface for simulating and signing transactions +#[derive(Clone)] +pub struct ManualSender { + pub sender: Addr, + pub chain_info: Arc, + pub grpc_channel: Channel, +} + +impl SenderBuilder for ManualSenderOptions { + type Error = DaemonError; + type Sender = ManualSender; + + async fn build(&self, chain_info: &Arc) -> Result { + let grpc_channel = GrpcChannel::from_chain_info(chain_info.as_ref()).await?; + Ok(ManualSender { + chain_info: chain_info.clone(), + sender: Addr::unchecked(self.sender_address.clone()), + grpc_channel, + }) + } +} + +impl QuerySender for ManualSender { + type Error = DaemonError; + type Options = ManualSenderOptions; + + fn channel(&self) -> tonic::transport::Channel { + self.grpc_channel.clone() + } +} + +impl TxSender for ManualSender { + async fn commit_tx_any( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> Result { + // We print the any messages to broadcast + println!("Here is the transaction to sign and broadcast: "); + println!("{:?}", msgs); + // We simulate + let gas_needed = self.simulate(msgs, memo).await?; + println!("Gas needed: {}", gas_needed); + + // We wait for the txhash as input to be able to continue the execution + println!("Enter the txhash to proceed"); + let mut txhash = String::new(); + io::stdout().flush().unwrap(); // Ensure the prompt is displayed + io::stdin() + .read_line(&mut txhash) + .expect("Failed to read line"); + let txhash = txhash.trim_end(); + + let resp = Node::new_async(self.channel()) + ._find_tx(txhash.to_string()) + .await?; + + assert_broadcast_code_cosm_response(resp) + } + + fn account_id(&self) -> AccountId { + self.sender.clone().to_string().parse().unwrap() + } +} + +impl ManualSender { + pub async fn simulate(&self, msgs: Vec, memo: Option<&str>) -> Result { + let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64; + + let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); + + let fee = TxBuilder::build_fee(0u8, &self.chain_info.gas_denom, 0, None)?; + + let BaseAccount { + account_number, + sequence, + pub_key, + .. + } = self.base_account().await?; + + let auth_info = SignerInfo { + public_key: pub_key.map(|key| key.try_into()).transpose()?, + mode_info: ModeInfo::single(SignMode::Direct), + sequence, + } + .auth_info(fee); + + let sign_doc = SignDoc::new( + &tx_body, + &auth_info, + &Id::try_from(self.chain_info.chain_id.to_string())?, + account_number, + )?; + + let tx_raw: Raw = cosmos::tx::v1beta1::TxRaw { + body_bytes: sign_doc.body_bytes, + auth_info_bytes: sign_doc.auth_info_bytes, + signatures: vec![vec![]], + } + .into(); + + Node::new_async(self.channel()) + ._simulate_tx(tx_raw.to_bytes()?) + .await + } + + async fn base_account(&self) -> Result { + let addr = self.address().to_string(); + + let mut client = + cosmrs::proto::cosmos::auth::v1beta1::query_client::QueryClient::new(self.channel()); + + let resp = client + .account(cosmrs::proto::cosmos::auth::v1beta1::QueryAccountRequest { address: addr }) + .await? + .into_inner(); + + let account = resp.account.unwrap().value; + + let acc = if let Ok(acc) = BaseAccount::decode(account.as_ref()) { + acc + } else if let Ok(acc) = PeriodicVestingAccount::decode(account.as_ref()) { + // try vesting account, (used by Terra2) + acc.base_vesting_account.unwrap().base_account.unwrap() + } else if let Ok(acc) = InjectiveEthAccount::decode(account.as_ref()) { + acc.base_account.unwrap() + } else { + return Err(DaemonError::StdErr( + "Unknown account type returned from QueryAccountRequest".into(), + )); + }; + + Ok(acc) + } +} diff --git a/cw-orch-daemon/examples/querier-daemon.rs b/cw-orch-daemon/examples/querier-daemon.rs new file mode 100644 index 000000000..b44d45ba8 --- /dev/null +++ b/cw-orch-daemon/examples/querier-daemon.rs @@ -0,0 +1,20 @@ +// ANCHOR: full_counter_example + +use cw_orch::{anyhow, prelude::*}; +use cw_orch_daemon::senders::QueryOnlyDaemon; + +// From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L1 +pub const LOCAL_JUNO_SENDER: &str = "juno16g2rahf5846rxzp3fwlswy08fz8ccuwk03k57y"; + +pub fn main() -> anyhow::Result<()> { + pretty_env_logger::init(); // Used to log contract and chain interactions + + let network = networks::LOCAL_JUNO; + // There is no need to register a mnemonic to use this daemon querier + let chain: QueryOnlyDaemon = QueryOnlyDaemon::builder(network).build_sender(())?; + + let balances = chain.bank_querier().balance(LOCAL_JUNO_SENDER, None)?; + assert!(!balances.is_empty()); + + Ok(()) +} diff --git a/cw-orch-daemon/src/builder.rs b/cw-orch-daemon/src/builder.rs index 650c94370..f2de6e60c 100644 --- a/cw-orch-daemon/src/builder.rs +++ b/cw-orch-daemon/src/builder.rs @@ -1,26 +1,23 @@ +use std::sync::Arc; + use crate::{ log::print_if_log_disabled, - sender::{SenderBuilder, SenderOptions}, - DaemonAsync, DaemonBuilder, DaemonStateFile, GrpcChannel, + senders::{builder::SenderBuilder, CosmosOptions, CosmosWalletKey}, + DaemonAsyncBase, DaemonBuilder, DaemonStateFile, Wallet, }; -use std::sync::Arc; - -use bitcoin::secp256k1::All; -use super::{error::DaemonError, sender::Sender, state::DaemonState}; +use super::{error::DaemonError, state::DaemonState}; use cw_orch_core::environment::ChainInfoOwned; - /// The default deployment id if none is provided pub const DEFAULT_DEPLOYMENT: &str = "default"; -#[derive(Clone, Default)] +#[derive(Clone)] /// Create [`DaemonAsync`] through [`DaemonAsyncBuilder`] /// ## Example /// ```no_run /// # tokio_test::block_on(async { /// use cw_orch_daemon::{DaemonAsyncBuilder, networks}; -/// let daemon = DaemonAsyncBuilder::default() -/// .chain(networks::LOCAL_JUNO) +/// let daemon = DaemonAsyncBuilder::new(networks::LOCAL_JUNO) /// .deployment_id("v0.1.0") /// .build() /// .await.unwrap(); @@ -28,7 +25,7 @@ pub const DEFAULT_DEPLOYMENT: &str = "default"; /// ``` pub struct DaemonAsyncBuilder { // # Required - pub(crate) chain: Option, + pub(crate) chain: ChainInfoOwned, // # Optional pub(crate) deployment_id: Option, pub(crate) state_path: Option, @@ -36,19 +33,19 @@ pub struct DaemonAsyncBuilder { pub(crate) state: Option, pub(crate) write_on_change: Option, - /* Sender related options */ - /// Wallet sender - /// Will be used in priority when set - pub(crate) sender: Option>, - /// Specify Daemon Sender Options - pub(crate) sender_options: SenderOptions, + pub(crate) mnemonic: Option, } impl DaemonAsyncBuilder { - /// Set the chain the daemon will connect to - pub fn chain(&mut self, chain: impl Into) -> &mut Self { - self.chain = Some(chain.into()); - self + pub fn new(chain: impl Into) -> Self { + Self { + chain: chain.into(), + deployment_id: None, + state_path: None, + state: None, + write_on_change: None, + mnemonic: None, + } } /// Set the deployment id to use for the daemon interactions @@ -58,40 +55,6 @@ impl DaemonAsyncBuilder { self } - /// Set the mnemonic to use with this chain. - /// Defaults to env variable depending on the environment. - /// - /// Variables: LOCAL_MNEMONIC, TEST_MNEMONIC and MAIN_MNEMONIC - pub fn mnemonic(&mut self, mnemonic: impl ToString) -> &mut Self { - self.sender = Some(SenderBuilder::Mnemonic(mnemonic.to_string())); - self - } - - /// Specifies a sender to use with this chain - /// This will be used in priority when set on the builder - pub fn sender(&mut self, wallet: Sender) -> &mut Self { - self.sender = Some(SenderBuilder::Sender(wallet)); - self - } - - /// Specifies whether authz should be used with this daemon - pub fn authz_granter(&mut self, granter: impl ToString) -> &mut Self { - self.sender_options.set_authz_granter(granter); - self - } - - /// Specifies whether a fee grant should be used with this daemon - pub fn fee_granter(&mut self, granter: impl ToString) -> &mut Self { - self.sender_options.set_fee_granter(granter); - self - } - - /// Specifies the hd_index of the daemon sender - pub fn hd_index(&mut self, index: u32) -> &mut Self { - self.sender_options.hd_index = Some(index); - self - } - /// Reuse already existent [`DaemonState`] /// Useful for multi-chain scenarios pub fn state(&mut self, state: DaemonState) -> &mut Self { @@ -108,6 +71,18 @@ impl DaemonAsyncBuilder { self } + /// Set the mnemonic used for the default Cosmos wallet + pub fn mnemonic(&mut self, mnemonic: impl Into) -> &mut Self { + self.mnemonic = Some(mnemonic.into()); + self + } + + /// Overwrite the chain info + pub fn chain(&mut self, chain: impl Into) -> &mut Self { + self.chain = chain.into(); + self + } + /// Specifies path to the daemon state file /// Defaults to env variable. /// @@ -118,21 +93,59 @@ impl DaemonAsyncBuilder { self } + /// Build a daemon with provided mnemonic or env-var mnemonic + pub async fn build(&self) -> Result, DaemonError> { + let chain_info = Arc::new(self.chain.clone()); + + let state = self.build_state()?; + // if mnemonic provided, use it. Else use env variables to retrieve mnemonic + + let options = CosmosOptions { + key: self.mnemonic.as_ref().map_or(CosmosWalletKey::Env, |m| { + CosmosWalletKey::Mnemonic(m.clone()) + }), + ..Default::default() + }; + let sender = options.build(&chain_info).await?; + + let daemon = DaemonAsyncBase::new(sender, state); + + print_if_log_disabled()?; + Ok(daemon) + } + /// Build a daemon - pub async fn build(&self) -> Result { - let chain_info = self - .chain - .clone() - .ok_or(DaemonError::BuilderMissing("chain information".into()))?; + pub async fn build_sender( + &self, + sender_options: T, + ) -> Result, DaemonError> { + let chain_info = Arc::new(self.chain.clone()); + + let state = self.build_state()?; + + let sender = sender_options + .build(&chain_info) + .await + .map_err(Into::into)?; + + let daemon = DaemonAsyncBase::new(sender, state); + + print_if_log_disabled()?; + Ok(daemon) + } + + /// Returns a built state + fn build_state(&self) -> Result { let deployment_id = self .deployment_id .clone() .unwrap_or(DEFAULT_DEPLOYMENT.to_string()); + let chain_info = Arc::new(self.chain.clone()); let state = match &self.state { Some(state) => { let mut state = state.clone(); - state.chain_data = chain_info.clone(); + state.chain_data = chain_info; state.deployment_id = deployment_id; if let Some(write_on_change) = self.write_on_change { state.write_on_change = write_on_change; @@ -159,42 +172,14 @@ impl DaemonAsyncBuilder { DaemonState::new( json_file_path, - chain_info.clone(), + &chain_info, deployment_id, false, self.write_on_change.unwrap_or(true), )? } }; - // if mnemonic provided, use it. Else use env variables to retrieve mnemonic - let sender_options = self.sender_options.clone(); - - let sender = match self.sender.clone() { - Some(sender) => match sender { - SenderBuilder::Mnemonic(mnemonic) => Sender::from_mnemonic_with_options( - chain_info.clone(), - GrpcChannel::connect(&chain_info.grpc_urls, &chain_info.chain_id).await?, - &mnemonic, - sender_options, - )?, - SenderBuilder::Sender(mut sender) => { - sender.set_options(self.sender_options.clone()); - sender - } - }, - None => Sender::new_with_options( - chain_info.clone(), - GrpcChannel::connect(&chain_info.grpc_urls, &chain_info.chain_id).await?, - sender_options, - )?, - }; - - let daemon = DaemonAsync { - state, - sender: Arc::new(sender), - }; - print_if_log_disabled()?; - Ok(daemon) + Ok(state) } } @@ -203,11 +188,10 @@ impl From for DaemonAsyncBuilder { DaemonAsyncBuilder { chain: value.chain, deployment_id: value.deployment_id, - sender_options: value.sender_options, - sender: value.sender, state: value.state, state_path: value.state_path, write_on_change: value.write_on_change, + mnemonic: value.mnemonic, } } } diff --git a/cw-orch-daemon/src/channel.rs b/cw-orch-daemon/src/channel.rs index 96cfb6837..5a3837ff0 100644 --- a/cw-orch-daemon/src/channel.rs +++ b/cw-orch-daemon/src/channel.rs @@ -1,7 +1,7 @@ use cosmrs::proto::cosmos::base::tendermint::v1beta1::{ service_client::ServiceClient, GetNodeInfoRequest, }; -use cw_orch_core::log::connectivity_target; +use cw_orch_core::{environment::ChainInfoOwned, log::connectivity_target}; use tonic::transport::{Channel, ClientTlsConfig}; use super::error::DaemonError; @@ -90,6 +90,11 @@ impl GrpcChannel { 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)] @@ -108,10 +113,9 @@ mod tests { let grpcs = &["https://127.0.0.1:99999"]; chain.grpc_urls = grpcs; - let build_res = DaemonAsync::builder() - .chain(chain) + let build_res = DaemonAsync::builder(chain) .deployment_id("v0.1.0") - .build() + .build_sender(()) .await; asserting!("there is no GRPC connection") @@ -128,10 +132,9 @@ mod tests { let grpcs = &[]; chain.grpc_urls = grpcs; - let build_res = DaemonAsync::builder() - .chain(chain) + let build_res = DaemonAsync::builder(chain) .deployment_id("v0.1.0") - .build() + .build_sender(()) .await; asserting!("GRPC list is empty") diff --git a/cw-orch-daemon/src/core.rs b/cw-orch-daemon/src/core.rs index 3f6851c12..e31cacc66 100644 --- a/cw-orch-daemon/src/core.rs +++ b/cw-orch-daemon/src/core.rs @@ -1,8 +1,11 @@ -use crate::{queriers::CosmWasm, DaemonState}; +use crate::{ + queriers::CosmWasm, + senders::{builder::SenderBuilder, query::QuerySender}, + DaemonAsyncBuilder, DaemonState, +}; use super::{ - builder::DaemonAsyncBuilder, cosmos_modules, error::DaemonError, queriers::Node, - sender::Wallet, tx_resp::CosmTxResponse, + cosmos_modules, error::DaemonError, queriers::Node, senders::Wallet, tx_resp::CosmTxResponse, }; use cosmrs::{ @@ -14,7 +17,7 @@ use cosmrs::{ use cosmwasm_std::{Addr, Binary, Coin}; use cw_orch_core::{ contract::interface_traits::Uploadable, - environment::{AsyncWasmQuerier, ChainState, IndexResponse, Querier}, + environment::{AsyncWasmQuerier, ChainInfoOwned, ChainState, IndexResponse, Querier}, log::transaction_target, }; use flate2::{write, Compression}; @@ -24,12 +27,17 @@ use serde_json::from_str; use std::{ fmt::Debug, io::Write, + ops::Deref, str::{from_utf8, FromStr}, time::Duration, }; use tonic::transport::Channel; +use crate::senders::tx::TxSender; + +pub const INSTANTIATE_2_TYPE_URL: &str = "/cosmwasm.wasm.v1.MsgInstantiateContract2"; + #[derive(Clone)] /** Represents a blockchain node. @@ -40,8 +48,7 @@ use tonic::transport::Channel; # tokio_test::block_on(async { use cw_orch_daemon::{DaemonAsync, networks}; - let daemon: DaemonAsync = DaemonAsync::builder() - .chain(networks::JUNO_1) + let daemon: DaemonAsync = DaemonAsync::builder(networks::JUNO_1) .build() .await.unwrap(); # }) @@ -59,25 +66,60 @@ use tonic::transport::Channel; This daemon is thread safe and can be used between threads. However, please make sure that you are not trying to broadcast multiple transactions at once when using this Daemon on different threads. - If you do so, you WILL get account sequence errors and your transactions won't get broadcasted. - Use a Mutex on top of this DaemonAsync to avoid such errors. + If you do so, you will get account sequence errors and your transactions won't get broadcasted. */ -pub struct DaemonAsync { +pub struct DaemonAsyncBase { /// Sender to send transactions to the chain - pub sender: Wallet, + sender: Sender, /// State of the daemon - pub state: DaemonState, + pub(crate) state: DaemonState, } -impl DaemonAsync { +pub type DaemonAsync = DaemonAsyncBase; + +impl DaemonAsyncBase { + pub(crate) fn new(sender: Sender, state: DaemonState) -> Self { + Self { sender, state } + } + + /// Get the communication service configured for this Daemon. + pub async fn service(&self) -> Result { + self.state.service().await + } + + pub fn chain_info(&self) -> &ChainInfoOwned { + self.state.chain_data.as_ref() + } + /// Get the daemon builder - pub fn builder() -> DaemonAsyncBuilder { - DaemonAsyncBuilder::default() + pub fn builder(chain: impl Into) -> DaemonAsyncBuilder { + DaemonAsyncBuilder::new(chain) } - /// Get the channel configured for this DaemonAsync. - pub fn channel(&self) -> Channel { - self.sender.grpc_channel.clone() + /// Set the Sender for use with this Daemon + /// The sender will be configured with the chain's data. + pub async fn new_sender( + self, + sender_options: T, + ) -> DaemonAsyncBase { + let sender = sender_options + .build(&self.state.chain_data) + .await + .expect("Failed to build sender"); + DaemonAsyncBase { + sender, + state: self.state, + } + } + + /// Get a mutable Sender + pub fn sender_mut(&mut self) -> &mut Sender { + &mut self.sender + } + + // Get a read-only Sender + pub fn sender(&self) -> &Sender { + &self.sender } /// Flushes all the state related to the current chain @@ -85,9 +127,95 @@ impl DaemonAsync { pub fn flush_state(&mut self) -> Result<(), DaemonError> { self.state.flush() } + + /// Returns a new [`DaemonAsyncBuilder`] with the current configuration. + /// Does not consume the original [`DaemonAsync`]. + pub fn rebuild(&self) -> DaemonAsyncBuilder { + DaemonAsyncBuilder { + state: Some(self.state()), + chain: self.state.chain_data.deref().clone(), + deployment_id: Some(self.state.deployment_id.clone()), + state_path: None, + write_on_change: None, + mnemonic: None, + } + } } -impl ChainState for DaemonAsync { +impl DaemonAsyncBase { + /// Get the channel configured for this DaemonAsync. + pub fn channel(&self) -> Channel { + self.sender().channel() + } + + /// Query a contract. + 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)?, + }) + .await?; + + Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?) + } + + /// Wait for a given amount of blocks. + pub async fn wait_blocks(&self, amount: u64) -> Result<(), DaemonError> { + let mut last_height = Node::new_async(self.channel())._block_height().await?; + let end_height = last_height + amount; + + let average_block_speed = Node::new_async(self.channel()) + ._average_block_speed(Some(0.9)) + .await?; + + let wait_time = average_block_speed.mul_f64(amount as f64); + + // now wait for that amount of time + tokio::time::sleep(wait_time).await; + // now check every block until we hit the target + while last_height < end_height { + // wait + + tokio::time::sleep(average_block_speed).await; + + // ping latest block + last_height = Node::new_async(self.channel())._block_height().await?; + } + Ok(()) + } + + /// Wait for a given amount of seconds. + pub async fn wait_seconds(&self, secs: u64) -> Result<(), DaemonError> { + tokio::time::sleep(Duration::from_secs(secs)).await; + + Ok(()) + } + + /// Wait for the next block. + pub async fn next_block(&self) -> Result<(), DaemonError> { + self.wait_blocks(1).await + } + + /// Get the current block info. + pub async fn block_info(&self) -> Result { + let block = Node::new_async(self.channel())._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(), + }) + } +} + +impl ChainState for DaemonAsyncBase { type Out = DaemonState; fn state(&self) -> Self::Out { @@ -96,23 +224,10 @@ impl ChainState for DaemonAsync { } // Execute on the real chain, returns tx response. -impl DaemonAsync { +impl DaemonAsyncBase { /// Get the sender address - pub fn sender(&self) -> Addr { - self.sender.address().unwrap() - } - - /// Returns a new [`DaemonAsyncBuilder`] with the current configuration. - /// Does not consume the original [`DaemonAsync`]. - pub fn rebuild(&self) -> DaemonAsyncBuilder { - let mut builder = DaemonAsyncBuilder { - state: Some(self.state()), - ..Default::default() - }; - builder - .chain(self.sender.chain_info.clone()) - .sender((*self.sender).clone()); - builder + pub fn sender_addr(&self) -> Addr { + self.sender().address() } /// Execute a message on a contract. @@ -123,12 +238,16 @@ impl DaemonAsync { contract_address: &Addr, ) -> Result { let exec_msg: MsgExecuteContract = MsgExecuteContract { - sender: self.sender.msg_sender()?, + sender: self.sender().account_id(), contract: AccountId::from_str(contract_address.as_str())?, msg: serde_json::to_vec(&exec_msg)?, funds: parse_cw_coins(coins)?, }; - let result = self.sender.commit_tx(vec![exec_msg], None).await?; + let result = self + .sender() + .commit_tx(vec![exec_msg], None) + .await + .map_err(Into::into)?; log::info!(target: &transaction_target(), "Execution done: {:?}", result.txhash); Ok(result) @@ -143,18 +262,20 @@ impl DaemonAsync { admin: Option<&Addr>, coins: &[Coin], ) -> Result { - let sender = &self.sender; - let init_msg = MsgInstantiateContract { code_id, label: Some(label.unwrap_or("instantiate_contract").to_string()), admin: admin.map(|a| FromStr::from_str(a.as_str()).unwrap()), - sender: self.sender.msg_sender()?, + sender: self.sender().account_id(), msg: serde_json::to_vec(&init_msg)?, funds: parse_cw_coins(coins)?, }; - let result = sender.commit_tx(vec![init_msg], None).await?; + let result = self + .sender() + .commit_tx(vec![init_msg], None) + .await + .map_err(Into::into)?; log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash); @@ -171,51 +292,34 @@ impl DaemonAsync { coins: &[Coin], salt: Binary, ) -> Result { - let sender = &self.sender; - let init_msg = MsgInstantiateContract2 { code_id, label: label.unwrap_or("instantiate_contract").to_string(), admin: admin.map(Into::into).unwrap_or_default(), - sender: sender.address()?.to_string(), + sender: self.sender_addr().to_string(), msg: serde_json::to_vec(&init_msg)?, funds: proto_parse_cw_coins(coins)?, salt: salt.to_vec(), fix_msg: false, }; - let result = sender + let result = self + .sender() .commit_tx_any( vec![Any { - type_url: "/cosmwasm.wasm.v1.MsgInstantiateContract2".to_string(), + type_url: INSTANTIATE_2_TYPE_URL.to_string(), value: init_msg.encode_to_vec(), }], None, ) - .await?; + .await + .map_err(Into::into)?; log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash); Ok(result) } - /// Query a contract. - 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)?, - }) - .await?; - - Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?) - } - /// Migration a contract. pub async fn migrate( &self, @@ -224,71 +328,25 @@ impl DaemonAsync { contract_address: &Addr, ) -> Result { let exec_msg: MsgMigrateContract = MsgMigrateContract { - sender: self.sender.msg_sender()?, + sender: self.sender().account_id(), contract: AccountId::from_str(contract_address.as_str())?, msg: serde_json::to_vec(&migrate_msg)?, code_id: new_code_id, }; - let result = self.sender.commit_tx(vec![exec_msg], None).await?; + let result = self + .sender() + .commit_tx(vec![exec_msg], None) + .await + .map_err(Into::into)?; Ok(result) } - /// Wait for a given amount of blocks. - pub async fn wait_blocks(&self, amount: u64) -> Result<(), DaemonError> { - let mut last_height = Node::new_async(self.channel())._block_height().await?; - let end_height = last_height + amount; - - let average_block_speed = Node::new_async(self.channel()) - ._average_block_speed(Some(0.9)) - .await?; - - let wait_time = average_block_speed.mul_f64(amount as f64); - - // now wait for that amount of time - tokio::time::sleep(wait_time).await; - // now check every block until we hit the target - while last_height < end_height { - // wait - - tokio::time::sleep(average_block_speed).await; - - // ping latest block - last_height = Node::new_async(self.channel())._block_height().await?; - } - Ok(()) - } - - /// Wait for a given amount of seconds. - pub async fn wait_seconds(&self, secs: u64) -> Result<(), DaemonError> { - tokio::time::sleep(Duration::from_secs(secs)).await; - - Ok(()) - } - - /// Wait for the next block. - pub async fn next_block(&self) -> Result<(), DaemonError> { - self.wait_blocks(1).await - } - - /// Get the current block info. - pub async fn block_info(&self) -> Result { - let block = Node::new_async(self.channel())._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(), - }) - } - /// Upload a contract to the chain. pub async fn upload( &self, _uploadable: &T, ) -> Result { - let sender = &self.sender; - let wasm_path = ::wasm(&self.sender.chain_info); + let wasm_path = ::wasm(self.chain_info()); log::debug!(target: &transaction_target(), "Uploading file at {:?}", wasm_path); @@ -297,12 +355,16 @@ impl DaemonAsync { e.write_all(&file_contents)?; let wasm_byte_code = e.finish()?; let store_msg = cosmrs::cosmwasm::MsgStoreCode { - sender: self.sender.msg_sender()?, + sender: self.sender().account_id(), wasm_byte_code, instantiate_permission: None, }; - let result = sender.commit_tx(vec![store_msg], None).await?; + let result = self + .sender() + .commit_tx(vec![store_msg], None) + .await + .map_err(Into::into)?; log::info!(target: &transaction_target(), "Uploading done: {:?}", result.txhash); @@ -315,11 +377,6 @@ impl DaemonAsync { } Ok(result) } - - /// Set the sender to use with this DaemonAsync to be the given wallet - pub fn set_sender(&mut self, sender: &Wallet) { - self.sender = sender.clone(); - } } impl Querier for DaemonAsync { diff --git a/cw-orch-daemon/src/lib.rs b/cw-orch-daemon/src/lib.rs index f127bca9a..ead035524 100644 --- a/cw-orch-daemon/src/lib.rs +++ b/cw-orch-daemon/src/lib.rs @@ -1,29 +1,33 @@ +// TODO: Figure out better mutex locking for senders +#![allow(clippy::await_holding_lock)] //! `Daemon` and `DaemonAsync` execution environments. //! //! The `Daemon` type is a synchronous wrapper around the `DaemonAsync` type and can be used as a contract execution environment. - -pub mod builder; -pub mod channel; -pub mod core; -pub mod error; pub mod json_lock; /// Proto types for different blockchains pub mod proto; -pub mod sender; -pub mod state; -pub mod sync; -pub mod tx_resp; // expose these as mods as they can grow pub mod env; pub mod keys; pub mod live_mock; -mod log; pub mod queriers; +pub mod senders; +pub mod service; pub mod tx_broadcaster; pub mod tx_builder; + +mod builder; +mod channel; +mod core; +mod error; +mod log; +mod state; +mod sync; +mod tx_resp; + pub use self::{builder::*, channel::*, core::*, error::*, state::*, sync::*, tx_resp::*}; pub use cw_orch_networks::networks; -pub use sender::Wallet; +pub use senders::{query::QuerySender, tx::TxSender, CosmosOptions, Wallet}; pub use tx_builder::TxBuilder; mod cosmos_proto_patches; diff --git a/cw-orch-daemon/src/live_mock.rs b/cw-orch-daemon/src/live_mock.rs index e53d370a9..a3664c914 100644 --- a/cw-orch-daemon/src/live_mock.rs +++ b/cw-orch-daemon/src/live_mock.rs @@ -81,10 +81,7 @@ impl WasmMockQuerier { let handle = RUNTIME.handle(); match &request { QueryRequest::Wasm(x) => { - let querier = CosmWasm { - channel: self.channel.clone(), - rt_handle: Some(handle.clone()), - }; + let querier = CosmWasm::new_sync(self.channel.clone(), handle); match x { WasmQuery::Smart { contract_addr, msg } => { // We forward the request to the cosmwasm querier diff --git a/cw-orch-daemon/src/queriers.rs b/cw-orch-daemon/src/queriers.rs index 31150217f..627f591c1 100644 --- a/cw-orch-daemon/src/queriers.rs +++ b/cw-orch-daemon/src/queriers.rs @@ -12,8 +12,7 @@ //! use cw_orch_daemon::{queriers::Node, DaemonAsync, networks}; //! # tokio_test::block_on(async { //! // call the builder and configure it as you need -//! let daemon = DaemonAsync::builder() -//! .chain(networks::LOCAL_JUNO) +//! let daemon = DaemonAsync::builder(networks::LOCAL_JUNO) //! .build() //! .await.unwrap(); //! // now you can use the Node querier: @@ -56,7 +55,7 @@ mod staking; pub use authz::Authz; pub use bank::{cosmrs_to_cosmwasm_coins, Bank}; -pub use cosmwasm::CosmWasm; +pub use cosmwasm::{CosmWasm, CosmWasmBase}; pub use feegrant::FeeGrant; pub use ibc::Ibc; pub use node::Node; diff --git a/cw-orch-daemon/src/queriers/authz.rs b/cw-orch-daemon/src/queriers/authz.rs index 2b55ecb75..47b56d979 100644 --- a/cw-orch-daemon/src/queriers/authz.rs +++ b/cw-orch-daemon/src/queriers/authz.rs @@ -1,4 +1,4 @@ -use crate::{cosmos_modules, error::DaemonError, Daemon}; +use crate::{cosmos_modules, error::DaemonError, service::DaemonService, Daemon}; use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; use cw_orch_core::environment::{Querier, QuerierGetter}; use tokio::runtime::Handle; @@ -7,21 +7,21 @@ use tonic::transport::Channel; /// Queries for Cosmos AuthZ Module /// All the async function are prefixed with `_` pub struct Authz { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl Authz { - pub fn new(daemon: &Daemon) -> Self { - Self { - channel: daemon.channel(), + pub fn new(daemon: &Daemon) -> Result { + Ok(Self { + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), - } + }) } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } diff --git a/cw-orch-daemon/src/queriers/bank.rs b/cw-orch-daemon/src/queriers/bank.rs index d9c8579c9..8cd0264b5 100644 --- a/cw-orch-daemon/src/queriers/bank.rs +++ b/cw-orch-daemon/src/queriers/bank.rs @@ -1,4 +1,4 @@ -use crate::{cosmos_modules, error::DaemonError, Daemon}; +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}; @@ -8,20 +8,20 @@ use tonic::transport::Channel; /// Queries for Cosmos Bank Module /// All the async function are prefixed with `_` pub struct Bank { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl Bank { - pub fn new(daemon: &Daemon) -> Self { + pub fn new(daemon: &DaemonBase) -> Self { Self { - channel: daemon.channel(), + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), } } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } @@ -31,7 +31,7 @@ impl Querier for Bank { type Error = DaemonError; } -impl QuerierGetter for Daemon { +impl QuerierGetter for DaemonBase { fn querier(&self) -> Bank { Bank::new(self) } diff --git a/cw-orch-daemon/src/queriers/cosmwasm.rs b/cw-orch-daemon/src/queriers/cosmwasm.rs index 70c89b6ba..4f41ac9a6 100644 --- a/cw-orch-daemon/src/queriers/cosmwasm.rs +++ b/cw-orch-daemon/src/queriers/cosmwasm.rs @@ -1,6 +1,13 @@ use std::str::FromStr; +use std::time::Duration; +use crate::service::{DaemonChannel, DaemonChannelFactory, DaemonService, MyRetryPolicy}; use crate::{cosmos_modules, error::DaemonError, Daemon}; +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::{ @@ -13,45 +20,62 @@ use cw_orch_core::{ environment::{Querier, QuerierGetter, WasmQuerier}, }; use tokio::runtime::Handle; -use tonic::transport::Channel; +use tonic::transport::{Channel, Endpoint}; +use tower::reconnect::Reconnect; +use tower::retry::RetryLayer; +use tower::{MakeService as _, Service, ServiceBuilder}; /// Querier for the CosmWasm SDK module /// All the async function are prefixed with `_` -pub struct CosmWasm { - pub channel: Channel, +pub struct CosmWasmBase { + pub service: DaemonService, pub rt_handle: Option, + _sender: PhantomData, } -impl CosmWasm { - pub fn new(daemon: &Daemon) -> Self { +pub type CosmWasm = CosmWasmBase; + +impl CosmWasmBase { + pub fn new(daemon: &DaemonBase) -> Self { Self { - channel: daemon.channel(), + service: daemon.channel(), rt_handle: Some(daemon.rt_handle.clone()), + _sender: PhantomData, } } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service: channel, rt_handle: None, + _sender: PhantomData, + } + } + pub fn new_sync(service: DaemonService, handle: &Handle) -> Self { + Self { + service, + rt_handle: Some(handle.clone()), + _sender: PhantomData, } } } -impl QuerierGetter for Daemon { - fn querier(&self) -> CosmWasm { - CosmWasm::new(self) +impl QuerierGetter> for DaemonBase { + fn querier(&self) -> CosmWasmBase { + CosmWasmBase::new(self) } } -impl Querier for CosmWasm { +impl Querier for CosmWasmBase { type Error = DaemonError; } -impl CosmWasm { +impl CosmWasmBase { /// Query code_id by hash - pub async fn _code_id_hash(&self, code_id: u64) -> Result { + pub async fn _code_id_hash(&mut self, code_id: u64) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryCodeRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + + let mut client = QueryClient::new(&mut self.service); + let request = QueryCodeRequest { code_id }; let resp = client.code(request).await?.into_inner(); let contract_hash = resp.code_info.unwrap().data_hash; @@ -64,7 +88,7 @@ impl CosmWasm { address: impl Into, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryContractInfoRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryContractInfoRequest { address: address.into(), }; @@ -94,7 +118,7 @@ impl CosmWasm { pagination: Option, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryContractHistoryRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryContractHistoryRequest { address: address.into(), pagination, @@ -109,7 +133,7 @@ impl CosmWasm { query_data: Vec, ) -> Result, DaemonError> { use cosmos_modules::cosmwasm::{query_client::*, QuerySmartContractStateRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QuerySmartContractStateRequest { address: address.into(), query_data, @@ -128,7 +152,7 @@ impl CosmWasm { pagination: Option, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryAllContractStateRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryAllContractStateRequest { address: address.into(), pagination, @@ -139,7 +163,7 @@ impl CosmWasm { /// 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 mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryCodeRequest { code_id }; let response = client.code(request).await?.into_inner().code_info.unwrap(); @@ -149,7 +173,7 @@ impl CosmWasm { /// 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 mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryCodeRequest { code_id }; Ok(client.code(request).await?.into_inner().data) } @@ -160,7 +184,15 @@ impl CosmWasm { pagination: Option, ) -> Result, DaemonError> { use cosmos_modules::cosmwasm::{query_client::*, QueryCodesRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + + let reconnect_service: Reconnect = + Reconnect::new::(DaemonChannelFactory {}, self.service.clone()); + + // Build your service stack + let service = ServiceBuilder::new().service(reconnect_service); + + let mut client = QueryClient::new(service); + let request = QueryCodesRequest { pagination }; let response = client.codes(request).await?.into_inner().code_infos; @@ -175,7 +207,7 @@ impl CosmWasm { &self, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryPinnedCodesRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryPinnedCodesRequest { pagination: None }; Ok(client.pinned_codes(request).await?.into_inner()) } @@ -186,7 +218,7 @@ impl CosmWasm { code_id: u64, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryContractsByCodeRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryContractsByCodeRequest { code_id, pagination: None, @@ -201,7 +233,7 @@ impl CosmWasm { query_data: Vec, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryRawContractStateRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + let mut client: QueryClient = QueryClient::new(self.service.clone()); let request = QueryRawContractStateRequest { address: address.into(), query_data, @@ -214,13 +246,20 @@ impl CosmWasm { &self, ) -> Result { use cosmos_modules::cosmwasm::{query_client::*, QueryParamsRequest}; - let mut client: QueryClient = QueryClient::new(self.channel.clone()); + + let reconnect_service: Reconnect = + Reconnect::new::(DaemonChannelFactory {}, self.service.clone()); + + // Build your service stack + let service = ServiceBuilder::new().service(reconnect_service); + + let mut client = QueryClient::new(service); Ok(client.params(QueryParamsRequest {}).await?.into_inner()) } } -impl WasmQuerier for CosmWasm { - type Chain = Daemon; +impl WasmQuerier for CosmWasmBase { + type Chain = DaemonBase; fn code_id_hash(&self, code_id: u64) -> Result { self.rt_handle .as_ref() @@ -291,12 +330,12 @@ impl WasmQuerier for CosmWasm { fn local_hash< T: cw_orch_core::contract::interface_traits::Uploadable - + cw_orch_core::contract::interface_traits::ContractInstance, + + cw_orch_core::contract::interface_traits::ContractInstance>, >( &self, contract: &T, ) -> Result { - ::wasm(&contract.environment().daemon.sender.chain_info).checksum() + ::wasm(contract.environment().daemon.chain_info()).checksum() } } diff --git a/cw-orch-daemon/src/queriers/env.rs b/cw-orch-daemon/src/queriers/env.rs index ef6c4195b..28277c657 100644 --- a/cw-orch-daemon/src/queriers/env.rs +++ b/cw-orch-daemon/src/queriers/env.rs @@ -1,18 +1,13 @@ use cw_orch_core::environment::{EnvironmentInfo, EnvironmentQuerier}; -use crate::Daemon; +use crate::{senders::query::QuerySender, DaemonBase}; -impl EnvironmentQuerier for Daemon { +impl EnvironmentQuerier for DaemonBase { fn env_info(&self) -> EnvironmentInfo { + let info = self.daemon.chain_info(); EnvironmentInfo { - chain_id: self.daemon.sender.chain_info.chain_id.clone(), - chain_name: self - .daemon - .sender - .chain_info - .network_info - .chain_name - .clone(), + chain_id: info.chain_id.clone(), + chain_name: info.network_info.chain_name.clone(), deployment_id: self.daemon.state.deployment_id.clone(), } } diff --git a/cw-orch-daemon/src/queriers/feegrant.rs b/cw-orch-daemon/src/queriers/feegrant.rs index 5937075d4..5241cc01f 100644 --- a/cw-orch-daemon/src/queriers/feegrant.rs +++ b/cw-orch-daemon/src/queriers/feegrant.rs @@ -7,21 +7,21 @@ use tonic::transport::Channel; /// Querier for the Cosmos Gov module /// All the async function are prefixed with `_` pub struct FeeGrant { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl FeeGrant { - pub fn new(daemon: &Daemon) -> Self { - Self { - channel: daemon.channel(), + pub fn new(daemon: &Daemon) -> Result { + Ok(Self { + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), - } + }) } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } diff --git a/cw-orch-daemon/src/queriers/gov.rs b/cw-orch-daemon/src/queriers/gov.rs index 1fd4a5197..6fea6931c 100644 --- a/cw-orch-daemon/src/queriers/gov.rs +++ b/cw-orch-daemon/src/queriers/gov.rs @@ -7,21 +7,21 @@ use tonic::transport::Channel; /// Querier for the Cosmos Gov module /// All the async function are prefixed with `_` pub struct Gov { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl Gov { - pub fn new(daemon: &Daemon) -> Self { - Self { - channel: daemon.channel(), + pub fn new(daemon: &Daemon) -> Result { + Ok(Self { + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), - } + }) } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } diff --git a/cw-orch-daemon/src/queriers/ibc.rs b/cw-orch-daemon/src/queriers/ibc.rs index 986ee5ff7..954d1c51a 100644 --- a/cw-orch-daemon/src/queriers/ibc.rs +++ b/cw-orch-daemon/src/queriers/ibc.rs @@ -17,21 +17,21 @@ use tonic::transport::Channel; /// Querier for the Cosmos IBC module /// All the async function are prefixed with `_` pub struct Ibc { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl Ibc { - pub fn new(daemon: &Daemon) -> Self { - Self { - channel: daemon.channel(), + pub fn new(daemon: &Daemon) -> Result { + Ok(Self { + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), - } + }) } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } diff --git a/cw-orch-daemon/src/queriers/node.rs b/cw-orch-daemon/src/queriers/node.rs index 83365a842..fb55ec632 100644 --- a/cw-orch-daemon/src/queriers/node.rs +++ b/cw-orch-daemon/src/queriers/node.rs @@ -1,7 +1,8 @@ use std::{cmp::min, time::Duration}; use crate::{ - cosmos_modules, env::DaemonEnvVars, error::DaemonError, tx_resp::CosmTxResponse, Daemon, + cosmos_modules, env::DaemonEnvVars, error::DaemonError, senders::query::QuerySender, + service::DaemonService, tx_resp::CosmTxResponse, DaemonBase, }; use cosmrs::{ @@ -23,26 +24,26 @@ use tonic::transport::Channel; /// Supports queries for block and tx information /// All the async function are prefixed with `_` pub struct Node { - pub channel: Channel, + pub service: DaemonService, pub rt_handle: Option, } impl Node { - pub fn new(daemon: &Daemon) -> Self { + pub fn new(daemon: &DaemonBase) -> Self { Self { - channel: daemon.channel(), + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), } } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } } -impl QuerierGetter for Daemon { +impl QuerierGetter for DaemonBase { fn querier(&self) -> Node { Node::new(self) } diff --git a/cw-orch-daemon/src/queriers/staking.rs b/cw-orch-daemon/src/queriers/staking.rs index 63d8b8860..787a008db 100644 --- a/cw-orch-daemon/src/queriers/staking.rs +++ b/cw-orch-daemon/src/queriers/staking.rs @@ -12,21 +12,21 @@ 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 service: DaemonService, pub rt_handle: Option, } impl Staking { - pub fn new(daemon: &Daemon) -> Self { - Self { - channel: daemon.channel(), + pub fn new(daemon: &Daemon) -> Result { + Ok(Self { + service: daemon.service()?, rt_handle: Some(daemon.rt_handle.clone()), - } + }) } - pub fn new_async(channel: Channel) -> Self { + pub fn new_async(service: DaemonService) -> Self { Self { - channel, + service, rt_handle: None, } } diff --git a/cw-orch-daemon/src/senders/builder.rs b/cw-orch-daemon/src/senders/builder.rs new file mode 100644 index 000000000..bcdf9ea09 --- /dev/null +++ b/cw-orch-daemon/src/senders/builder.rs @@ -0,0 +1,18 @@ +use std::sync::Arc; + +use cw_orch_core::environment::ChainInfoOwned; + +use crate::DaemonError; + +/// Allows building a `Sender` from `SenderBuilder::Options` +/// `async`` because it could do network requests during build +pub trait SenderBuilder { + type Error: Into + std::error::Error + std::fmt::Debug + Send + Sync + 'static; + type Sender; + + /// Build a new `Sender`. + fn build( + &self, + chain_info: &Arc, + ) -> impl std::future::Future> + Send; +} diff --git a/cw-orch-daemon/src/sender.rs b/cw-orch-daemon/src/senders/cosmos.rs similarity index 67% rename from cw-orch-daemon/src/sender.rs rename to cw-orch-daemon/src/senders/cosmos.rs index 2cc790f9b..3aa01cc04 100644 --- a/cw-orch-daemon/src/sender.rs +++ b/cw-orch-daemon/src/senders/cosmos.rs @@ -2,20 +2,22 @@ use crate::{ env::DaemonEnvVars, proto::injective::ETHEREUM_COIN_TYPE, queriers::Bank, + service::{DaemonChannel, DaemonChannelFactory, DaemonService, DaemonServiceCreation}, tx_broadcaster::{ account_sequence_strategy, assert_broadcast_code_cosm_response, insufficient_fee_strategy, TxBroadcaster, }, + CosmosOptions, GrpcChannel, }; -use super::{ +use crate::proto::injective::InjectiveEthAccount; +use crate::{ cosmos_modules::{self, auth::BaseAccount}, error::DaemonError, queriers::Node, tx_builder::TxBuilder, tx_resp::CosmTxResponse, }; -use crate::proto::injective::InjectiveEthAccount; #[cfg(feature = "eth")] use crate::proto::injective::InjectiveSigner; @@ -29,169 +31,106 @@ use cosmrs::{ tx::{self, ModeInfo, Msg, Raw, SignDoc, SignMode, SignerInfo}, AccountId, Any, }; -use cosmwasm_std::{coin, Addr, Coin}; +use cosmwasm_std::{coin, Coin}; use cw_orch_core::{ environment::{ChainInfoOwned, ChainKind}, - log::local_target, CoreEnvVars, CwEnvError, }; use crate::env::{LOCAL_MNEMONIC_ENV_NAME, MAIN_MNEMONIC_ENV_NAME, TEST_MNEMONIC_ENV_NAME}; -use bitcoin::secp256k1::{All, Context, Secp256k1, Signing}; +use bitcoin::secp256k1::{All, Secp256k1, Signing}; use std::{str::FromStr, sync::Arc}; use cosmos_modules::vesting::PeriodicVestingAccount; use tonic::transport::Channel; +use super::{cosmos_options::CosmosWalletKey, query::QuerySender, tx::TxSender}; + const GAS_BUFFER: f64 = 1.3; const BUFFER_THRESHOLD: u64 = 200_000; const SMALL_GAS_BUFFER: f64 = 1.4; -/// This enum allows for choosing which sender type will be constructed in a DaemonBuilder -#[derive(Clone)] -pub enum SenderBuilder { - Sender(Sender), - Mnemonic(String), -} - /// A wallet is a sender of transactions, can be safely cloned and shared within the same thread. -pub type Wallet = Arc>; +pub type Wallet = CosmosSender; /// Signer of the transactions and helper for address derivation /// This is the main interface for simulating and signing transactions #[derive(Clone)] -pub struct Sender { +pub struct CosmosSender { pub private_key: PrivateKey, - pub secp: Secp256k1, /// gRPC channel pub grpc_channel: Channel, /// Information about the chain - pub chain_info: ChainInfoOwned, - pub(crate) options: SenderOptions, -} - -/// Options for how txs should be constructed for this sender. -#[derive(Default, Clone)] -#[non_exhaustive] -pub struct SenderOptions { - pub authz_granter: Option, - pub fee_granter: Option, - pub hd_index: Option, + pub chain_info: Arc, + pub(crate) options: CosmosOptions, + pub secp: Secp256k1, } -impl SenderOptions { - pub fn authz_granter(mut self, granter: impl ToString) -> Self { - self.authz_granter = Some(granter.to_string()); - self - } - pub fn fee_granter(mut self, granter: impl ToString) -> Self { - self.fee_granter = Some(granter.to_string()); - self - } - pub fn hd_index(mut self, index: u32) -> Self { - self.hd_index = Some(index); - self - } - pub fn set_authz_granter(&mut self, granter: impl ToString) { - self.authz_granter = Some(granter.to_string()); - } - pub fn set_fee_granter(&mut self, granter: impl ToString) { - self.fee_granter = Some(granter.to_string()); - } - pub fn set_hd_index(&mut self, index: u32) { - self.hd_index = Some(index); - } -} +impl Wallet { + pub async fn new( + chain_info: &Arc, + options: CosmosOptions, + ) -> Result { + let secp = Secp256k1::new(); -impl Sender { - pub fn new(chain_info: ChainInfoOwned, channel: Channel) -> Result, DaemonError> { - Self::new_with_options(chain_info, channel, SenderOptions::default()) - } + let pk_from_mnemonic = |mnemonic: &str| -> Result { + PrivateKey::from_words( + &secp, + mnemonic, + 0, + options.hd_index.unwrap_or(0), + chain_info.network_info.coin_type, + ) + }; - pub fn channel(&self) -> Channel { - self.grpc_channel.clone() - } + let pk: PrivateKey = match &options.key { + CosmosWalletKey::Mnemonic(mnemonic) => pk_from_mnemonic(mnemonic)?, + CosmosWalletKey::Env => { + let mnemonic = get_mnemonic_env(&chain_info.kind)?; + pk_from_mnemonic(&mnemonic)? + } + CosmosWalletKey::RawKey(bytes) => PrivateKey::from_raw_key( + &secp, + bytes, + 0, + options.hd_index.unwrap_or(0), + chain_info.network_info.coin_type, + )?, + }; - pub fn new_with_options( - chain_info: ChainInfoOwned, - channel: Channel, - options: SenderOptions, - ) -> Result, DaemonError> { - let mnemonic = get_mnemonic_env(&chain_info.kind)?; + // ensure address is valid + AccountId::new( + &chain_info.network_info.pub_address_prefix, + &pk.public_key(&secp).raw_address.unwrap(), + )?; - Self::from_mnemonic_with_options(chain_info, channel, &mnemonic, options) + Ok(Self { + chain_info: chain_info.clone(), + grpc_channel: GrpcChannel::from_chain_info(chain_info.as_ref()).await?, + private_key: pk, + secp, + options, + }) } - /// Construct a new Sender from a mnemonic with additional options - pub fn from_mnemonic( - chain_info: ChainInfoOwned, - channel: Channel, + /// Construct a new Sender from a mnemonic + pub async fn from_mnemonic( + chain_info: &Arc, mnemonic: &str, - ) -> Result, DaemonError> { - Self::from_mnemonic_with_options(chain_info, channel, mnemonic, SenderOptions::default()) + ) -> Result { + let options = CosmosOptions { + key: CosmosWalletKey::Mnemonic(mnemonic.to_string()), + ..Default::default() + }; + Self::new(chain_info, options).await } - /// Construct a new Sender from a mnemonic with additional options - pub fn from_mnemonic_with_options( - chain_info: ChainInfoOwned, - channel: Channel, - mnemonic: &str, - options: SenderOptions, - ) -> Result, DaemonError> { - let secp = Secp256k1::new(); - let p_key: PrivateKey = PrivateKey::from_words( - &secp, - mnemonic, - 0, - options.hd_index.unwrap_or(0), - chain_info.network_info.coin_type, - )?; - - let sender = Sender { - chain_info, - grpc_channel: channel, - private_key: p_key, - secp, - options, - }; - log::info!( - target: &local_target(), - "Interacting with {} using address: {}", - sender.chain_info.chain_id, - sender.pub_addr_str()? - ); - Ok(sender) + pub fn channel(&self) -> Channel { + self.grpc_channel.clone() } - /// Construct a new Sender from a raw key with additional options - pub fn from_raw_key_with_options( - chain_info: ChainInfoOwned, - channel: Channel, - raw_key: &[u8], - options: SenderOptions, - ) -> Result, DaemonError> { - let secp = Secp256k1::new(); - let p_key: PrivateKey = PrivateKey::from_raw_key( - &secp, - raw_key, - 0, - options.hd_index.unwrap_or(0), - chain_info.network_info.coin_type, - )?; - let sender = Sender { - private_key: p_key, - secp, - options, - grpc_channel: channel, - chain_info, - }; - log::info!( - target: &local_target(), - "Interacting with {} using address: {}", - sender.chain_info.chain_id, - sender.pub_addr_str()? - ); - Ok(sender) + pub fn options(&self) -> CosmosOptions { + self.options.clone() } pub fn set_authz_granter(&mut self, granter: impl Into) { @@ -202,50 +141,24 @@ impl Sender { self.options.fee_granter = Some(granter.into()); } - pub fn set_options(&mut self, options: SenderOptions) { - if options.hd_index.is_some() { - // Need to generate new sender as hd_index impacts private key - let new_sender = Sender::from_raw_key_with_options( - self.chain_info.clone(), - self.channel(), - &self.private_key.raw_key(), - options, - ) - .unwrap(); - *self = new_sender - } else { - self.options = options - } + pub fn pub_addr_str(&self) -> String { + self.account_id().to_string() } - fn cosmos_private_key(&self) -> SigningKey { - SigningKey::from_slice(&self.private_key.raw_key()).unwrap() - } - - pub fn pub_addr(&self) -> Result { - Ok(AccountId::new( - &self.chain_info.network_info.pub_address_prefix, - &self.private_key.public_key(&self.secp).raw_address.unwrap(), - )?) - } - - pub fn address(&self) -> Result { - Ok(Addr::unchecked(self.pub_addr_str()?)) - } - - pub fn pub_addr_str(&self) -> Result { - Ok(self.pub_addr()?.to_string()) - } + pub async fn broadcast_tx( + &self, + tx: Raw, + ) -> Result { + let mut client = cosmos_modules::tx::service_client::ServiceClient::new(self.channel()); + let commit = client + .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { + tx_bytes: tx.to_bytes()?, + mode: cosmos_modules::tx::BroadcastMode::Sync.into(), + }) + .await?; - /// Returns the actual sender of every message sent. - /// If an authz granter is set, returns the authz granter - /// Else, returns the address associated with the current private key - pub fn msg_sender(&self) -> Result { - if let Some(sender) = &self.options.authz_granter { - Ok(sender.parse()?) - } else { - self.pub_addr() - } + let commit = commit.into_inner().tx_response.unwrap(); + Ok(commit) } pub async fn bank_send( @@ -253,8 +166,14 @@ impl Sender { recipient: &str, coins: Vec, ) -> Result { + let acc_id = if let Some(granter) = self.options.authz_granter.as_ref() { + AccountId::from_str(granter).unwrap() + } else { + self.account_id() + }; + let msg_send = MsgSend { - from_address: self.msg_sender()?, + from_address: acc_id, to_address: AccountId::from_str(recipient)?, amount: parse_cw_coins(&coins)?, }; @@ -262,29 +181,6 @@ impl Sender { self.commit_tx(vec![msg_send], Some("sending tokens")).await } - pub(crate) fn get_fee_token(&self) -> String { - self.chain_info.gas_denom.to_string() - } - - /// Compute the gas fee from the expected gas in the transaction - /// Applies a Gas Buffer for including signature verification - pub(crate) fn get_fee_from_gas(&self, gas: u64) -> Result<(u64, u128), DaemonError> { - let mut gas_expected = if let Some(gas_buffer) = DaemonEnvVars::gas_buffer() { - gas as f64 * gas_buffer - } else if gas < BUFFER_THRESHOLD { - gas as f64 * SMALL_GAS_BUFFER - } else { - gas as f64 * GAS_BUFFER - }; - - if let Some(min_gas) = DaemonEnvVars::min_gas() { - gas_expected = (min_gas as f64).max(gas_expected); - } - let fee_amount = gas_expected * (self.chain_info.gas_price + 0.00001); - - Ok((gas_expected as u64, fee_amount as u128)) - } - /// Computes the gas needed for submitting a transaction pub async fn calculate_gas( &self, @@ -292,7 +188,12 @@ impl Sender { sequence: u64, account_number: u64, ) -> Result { - let fee = TxBuilder::build_fee(0u8, &self.chain_info.gas_denom, 0, self.options.clone())?; + let fee = TxBuilder::build_fee( + 0u8, + &self.chain_info.gas_denom, + 0, + self.options.fee_granter.clone(), + )?; let auth_info = SignerInfo { public_key: self.private_key.get_signer_public_key(&self.secp), @@ -310,7 +211,7 @@ impl Sender { let tx_raw = self.sign(sign_doc)?; - Node::new_async(self.channel()) + Node::new_async(self.channel()?) ._simulate_tx(tx_raw.to_bytes()?) .await } @@ -322,7 +223,7 @@ impl Sender { msgs: Vec, memo: Option<&str>, ) -> Result<(u64, Coin), DaemonError> { - let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64; + let timeout_height = Node::new_async(self.channel()?)._block_height().await? + 10u64; let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); @@ -355,48 +256,6 @@ impl Sender { self.commit_tx_any(msgs, memo).await } - pub async fn commit_tx_any( - &self, - msgs: Vec, - memo: Option<&str>, - ) -> Result { - let timeout_height = Node::new_async(self.channel())._block_height().await? + 10u64; - - let msgs = if self.options.authz_granter.is_some() { - // We wrap authz messages - vec![Any { - type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(), - value: MsgExec { - grantee: self.pub_addr_str()?, - msgs, - } - .encode_to_vec(), - }] - } else { - msgs - }; - - let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); - - let tx_builder = TxBuilder::new(tx_body); - - // We retry broadcasting the tx, with the following strategies - // 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user) - // 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx) - // 3. In case there is an other error, we fail - let tx_response = TxBroadcaster::default() - .add_strategy(insufficient_fee_strategy()) - .add_strategy(account_sequence_strategy()) - .broadcast(tx_builder, self) - .await?; - - let resp = Node::new_async(self.channel()) - ._find_tx(tx_response.txhash) - .await?; - - assert_broadcast_code_cosm_response(resp) - } - pub fn sign(&self, sign_doc: SignDoc) -> Result { let tx_raw = if self.private_key.coin_type == ETHEREUM_COIN_TYPE { #[cfg(not(feature = "eth"))] @@ -413,7 +272,7 @@ impl Sender { } pub async fn base_account(&self) -> Result { - let addr = self.pub_addr().unwrap().to_string(); + let addr = self.address().to_string(); let mut client = cosmos_modules::auth::query_client::QueryClient::new(self.channel()); @@ -440,22 +299,6 @@ impl Sender { Ok(acc) } - pub async fn broadcast_tx( - &self, - tx: Raw, - ) -> Result { - let mut client = cosmos_modules::tx::service_client::ServiceClient::new(self.channel()); - let commit = client - .broadcast_tx(cosmos_modules::tx::BroadcastTxRequest { - tx_bytes: tx.to_bytes()?, - mode: cosmos_modules::tx::BroadcastMode::Sync.into(), - }) - .await?; - - let commit = commit.into_inner().tx_response.unwrap(); - Ok(commit) - } - /// Allows for checking wether the sender is able to broadcast a transaction that necessitates the provided `gas` pub async fn has_enough_balance_for_gas(&self, gas: u64) -> Result<(), DaemonError> { let (_gas_expected, fee_amount) = self.get_fee_from_gas(gas)?; @@ -472,7 +315,7 @@ impl Sender { let bank = Bank::new_async(self.channel()); let balance = bank - ._balance(self.address()?, Some(fee.denom.clone())) + ._balance(self.address(), Some(fee.denom.clone())) .await?[0] .clone(); @@ -480,7 +323,7 @@ impl Sender { "Checking balance {} on chain {}, address {}. Expecting {}{}", balance.amount, chain_info.chain_id, - self.address()?, + self.address(), fee, fee.denom ); @@ -496,7 +339,7 @@ impl Sender { Needed: {}{} but only have: {}. Press 'y' when the wallet balance has been increased to resume deployment", chain_info.chain_id, - self.address()?, + self.address(), fee, fee.denom, balance @@ -522,6 +365,106 @@ impl Sender { }); } } + + pub(crate) fn get_fee_token(&self) -> String { + self.chain_info.gas_denom.to_string() + } + + fn cosmos_private_key(&self) -> SigningKey { + SigningKey::from_slice(&self.private_key.raw_key()).unwrap() + } + + /// Compute the gas fee from the expected gas in the transaction + /// Applies a Gas Buffer for including signature verification + pub(crate) fn get_fee_from_gas(&self, gas: u64) -> Result<(u64, u128), DaemonError> { + let mut gas_expected = if let Some(gas_buffer) = DaemonEnvVars::gas_buffer() { + gas as f64 * gas_buffer + } else if gas < BUFFER_THRESHOLD { + gas as f64 * SMALL_GAS_BUFFER + } else { + gas as f64 * GAS_BUFFER + }; + + if let Some(min_gas) = DaemonEnvVars::min_gas() { + gas_expected = (min_gas as f64).max(gas_expected); + } + let fee_amount = gas_expected * (self.chain_info.gas_price + 0.00001); + + Ok((gas_expected as u64, fee_amount as u128)) + } +} + +impl QuerySender for Wallet { + type Error = DaemonError; + type Options = CosmosOptions; + + fn channel(&self) -> Channel { + self.channel() + } +} + +impl TxSender for Wallet { + async fn commit_tx_any( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> Result { + let timeout_height = Node::new_async(self.channel()?)._block_height().await? + 10u64; + + let msgs = if self.options.authz_granter.is_some() { + // We wrap authz messages + vec![Any { + type_url: "/cosmos.authz.v1beta1.MsgExec".to_string(), + value: MsgExec { + grantee: self.pub_addr_str(), + msgs, + } + .encode_to_vec(), + }] + } else { + msgs + }; + + let tx_body = TxBuilder::build_body(msgs, memo, timeout_height); + + let tx_builder = TxBuilder::new(tx_body); + + // We retry broadcasting the tx, with the following strategies + // 1. In case there is an `incorrect account sequence` error, we can retry as much as possible (doesn't cost anything to the user) + // 2. In case there is an insufficient_fee error, we retry once (costs fee to the user everytime we submit this kind of tx) + // 3. In case there is an other error, we fail + let tx_response = TxBroadcaster::default() + .add_strategy(insufficient_fee_strategy()) + .add_strategy(account_sequence_strategy()) + .broadcast(tx_builder, self) + .await?; + + let resp = Node::new_async(self.channel()?) + ._find_tx(tx_response.txhash) + .await?; + + assert_broadcast_code_cosm_response(resp) + } + + fn account_id(&self) -> AccountId { + AccountId::new( + &self.chain_info.network_info.pub_address_prefix, + &self.private_key.public_key(&self.secp).raw_address.unwrap(), + ) + // unwrap as address is validated on construction + .unwrap() + } +} + +impl DaemonServiceCreation for Sender { + async fn channel(&self) -> Result { + let channel = + GrpcChannel::connect(&self.chain_info.grpc_urls, &self.chain_info.chain_id).await?; + Ok(DaemonService::new::( + DaemonChannelFactory {}, + channel, + )) + } } fn get_mnemonic_env(chain_kind: &ChainKind) -> Result { diff --git a/cw-orch-daemon/src/senders/cosmos_batch.rs b/cw-orch-daemon/src/senders/cosmos_batch.rs new file mode 100644 index 000000000..d939ff20c --- /dev/null +++ b/cw-orch-daemon/src/senders/cosmos_batch.rs @@ -0,0 +1,135 @@ +use crate::{DaemonBase, INSTANTIATE_2_TYPE_URL}; + +use crate::{error::DaemonError, tx_resp::CosmTxResponse}; + +use cosmrs::proto::cosmwasm::wasm::v1::{MsgInstantiateContract, MsgStoreCode}; +use cosmrs::{AccountId, Any}; +use cosmwasm_std::Addr; +use cw_orch_core::environment::ChainInfoOwned; +use cw_orch_core::log::transaction_target; +use options::CosmosBatchOptions; +use prost::Name; + +use std::sync::{Arc, Mutex}; + +use super::builder::SenderBuilder; +use super::cosmos::Wallet; +use super::query::QuerySender; +use super::tx::TxSender; + +pub type BatchDaemon = DaemonBase; + +pub mod options { + use super::super::CosmosOptions; + + #[derive(Clone, Default)] + pub struct CosmosBatchOptions(pub(crate) CosmosOptions); + + impl From for CosmosBatchOptions { + fn from(options: CosmosOptions) -> Self { + Self(options) + } + } + + impl CosmosBatchOptions { + pub fn new(options: CosmosOptions) -> Self { + Self(options) + } + } +} + +/// Signer of Message batch transactions +/// This is a wrapper around the `Wallet` struct, with the addition of a `msgs` field that cache messages before they are sent. +#[derive(Clone)] +pub struct CosmosBatchSender { + /// Contains the different messages to broadcast + pub msgs: Arc>>, + pub sender: Wallet, +} + +impl CosmosBatchSender { + /// Broadcast the cached messages in a transaction. + pub async fn broadcast(&self, memo: Option<&str>) -> Result { + let msgs = self.msgs.lock().unwrap().to_vec(); + log::info!( + target: &transaction_target(), + "[Broadcast] {} msgs in a single transaction", + msgs.len() + ); + let tx_result = self.sender.commit_tx_any(msgs, memo).await?; + log::info!( + target: &transaction_target(), + "[Broadcasted] Success: {}", + tx_result.txhash + ); + + let mut msgs_to_empty = self.msgs.lock().unwrap(); + *msgs_to_empty = vec![]; + + Ok(tx_result) + } +} + +impl SenderBuilder for CosmosBatchOptions { + type Error = DaemonError; + type Sender = CosmosBatchSender; + + async fn build(&self, chain_info: &Arc) -> Result { + Ok(CosmosBatchSender { + msgs: Default::default(), + sender: self.0.build(chain_info).await?, + }) + } +} + +impl QuerySender for CosmosBatchSender { + type Error = DaemonError; + type Options = CosmosBatchOptions; + + fn channel(&self) -> tonic::transport::Channel { + self.sender.channel() + } +} + +impl TxSender for CosmosBatchSender { + async fn commit_tx_any( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> Result { + // We check the type URLS. We can safely put them inside the lock if they DON'T correspond to the following: + // - Code Upload + // - Contract Instantiation (1 and 2) + + let broadcast_immediately_type_urls = [ + MsgStoreCode::type_url(), + MsgInstantiateContract::type_url(), + INSTANTIATE_2_TYPE_URL.to_string(), + ]; + + let broadcast_immediately = msgs + .iter() + .any(|msg| broadcast_immediately_type_urls.contains(&msg.type_url)); + + if broadcast_immediately { + self.sender.commit_tx_any(msgs, memo).await + } else { + log::info!( + target: &transaction_target(), + "Transaction not sent, use `DaemonBase::wallet().broadcast(), to broadcast the batched transactions", + ); + let mut msg_storage = self.msgs.lock().unwrap(); + msg_storage.extend(msgs); + + Ok(CosmTxResponse::default()) + } + } + + fn address(&self) -> Addr { + self.sender.address() + } + + fn account_id(&self) -> AccountId { + self.sender.account_id() + } +} diff --git a/cw-orch-daemon/src/senders/cosmos_options.rs b/cw-orch-daemon/src/senders/cosmos_options.rs new file mode 100644 index 000000000..741a6015f --- /dev/null +++ b/cw-orch-daemon/src/senders/cosmos_options.rs @@ -0,0 +1,86 @@ +use std::{str::FromStr, sync::Arc}; + +use cosmrs::AccountId; +use cw_orch_core::environment::ChainInfoOwned; + +use crate::{DaemonError, Wallet}; + +use super::{builder::SenderBuilder, CosmosSender}; + +/// Options for how txs should be constructed for this sender. +#[derive(Default, Clone)] +#[non_exhaustive] +pub struct CosmosOptions { + pub authz_granter: Option, + pub fee_granter: Option, + pub hd_index: Option, + /// Used to derive the private key + pub(crate) key: CosmosWalletKey, +} + +#[derive(Default, Clone)] +pub enum CosmosWalletKey { + Mnemonic(String), + RawKey(Vec), + #[default] + Env, +} + +impl CosmosOptions { + pub fn check(&self) -> Result<(), DaemonError> { + if let Some(addr) = &self.authz_granter { + AccountId::from_str(addr)?; + } + + if let Some(addr) = &self.fee_granter { + AccountId::from_str(addr)?; + } + + Ok(()) + } + + pub fn authz_granter(mut self, granter: impl ToString) -> Self { + self.authz_granter = Some(granter.to_string()); + self + } + + pub fn fee_granter(mut self, granter: impl ToString) -> Self { + self.fee_granter = Some(granter.to_string()); + self + } + + pub fn hd_index(mut self, index: u32) -> Self { + self.hd_index = Some(index); + self + } + + pub fn mnemonic(mut self, mnemonic: impl ToString) -> Self { + self.key = CosmosWalletKey::Mnemonic(mnemonic.to_string()); + self + } + + pub fn set_authz_granter(&mut self, granter: impl ToString) { + self.authz_granter = Some(granter.to_string()); + } + + pub fn set_fee_granter(&mut self, granter: impl ToString) { + self.fee_granter = Some(granter.to_string()); + } + + pub fn set_hd_index(&mut self, index: u32) { + self.hd_index = Some(index); + } + + pub fn set_mnemonic(&mut self, mnemonic: impl ToString) { + self.key = CosmosWalletKey::Mnemonic(mnemonic.to_string()); + } +} + +impl SenderBuilder for CosmosOptions { + type Error = DaemonError; + type Sender = Wallet; + + async fn build(&self, chain_info: &Arc) -> Result { + CosmosSender::new(chain_info, self.clone()).await + } +} diff --git a/cw-orch-daemon/src/senders/mod.rs b/cw-orch-daemon/src/senders/mod.rs new file mode 100644 index 000000000..e637e4470 --- /dev/null +++ b/cw-orch-daemon/src/senders/mod.rs @@ -0,0 +1,17 @@ +// Core Sender traits +pub mod builder; +pub mod query; +pub mod tx; + +// Senders +mod cosmos; +mod cosmos_batch; +mod cosmos_options; +mod query_only; + +pub use { + cosmos::{CosmosSender, Wallet}, + cosmos_batch::{options::CosmosBatchOptions, BatchDaemon, CosmosBatchSender}, + cosmos_options::{CosmosOptions, CosmosWalletKey}, + query_only::{QueryOnlyDaemon, QueryOnlySender}, +}; diff --git a/cw-orch-daemon/src/senders/query.rs b/cw-orch-daemon/src/senders/query.rs new file mode 100644 index 000000000..208c3f0a7 --- /dev/null +++ b/cw-orch-daemon/src/senders/query.rs @@ -0,0 +1,15 @@ +use tonic::transport::Channel; + +use crate::DaemonError; + +use super::builder::SenderBuilder; + +/// A sender that can query information over a connection. +pub trait QuerySender: Clone { + type Error: Into + std::error::Error + std::fmt::Debug + Send + Sync + 'static; + /// Options for this sender + type Options: SenderBuilder; + + /// Get the channel for the sender + fn channel(&self) -> Channel; +} diff --git a/cw-orch-daemon/src/senders/query_only.rs b/cw-orch-daemon/src/senders/query_only.rs new file mode 100644 index 000000000..1f30ede1d --- /dev/null +++ b/cw-orch-daemon/src/senders/query_only.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use crate::{error::DaemonError, DaemonBase, GrpcChannel}; + +use cw_orch_core::environment::ChainInfoOwned; + +use tonic::transport::Channel; + +use super::{builder::SenderBuilder, query::QuerySender}; + +/// Daemon that does not support signing. +/// Will err on any attempt to sign a transaction or retrieve a sender address. +pub type QueryOnlyDaemon = DaemonBase; + +/// Signer of the transactions and helper for address derivation +#[derive(Clone)] +pub struct QueryOnlySender { + /// gRPC channel + pub channel: Channel, + /// Information about the chain + pub chain_info: Arc, +} + +impl SenderBuilder for () { + type Error = DaemonError; + type Sender = QueryOnlySender; + + async fn build(&self, chain_info: &Arc) -> Result { + let channel = GrpcChannel::from_chain_info(chain_info.as_ref()).await?; + + Ok(QueryOnlySender { + channel, + chain_info: chain_info.clone(), + }) + } +} + +impl QuerySender for QueryOnlySender { + type Error = DaemonError; + type Options = (); + + fn channel(&self) -> Channel { + self.channel.clone() + } +} + +#[cfg(test)] +mod tests { + use cw_orch_networks::networks::OSMOSIS_1; + + use super::QueryOnlyDaemon; + use crate::DaemonBuilder; + + #[test] + #[serial_test::serial] + fn build() { + let _query_only_daemon: QueryOnlyDaemon = + DaemonBuilder::new(OSMOSIS_1).build_sender(()).unwrap(); + } +} diff --git a/cw-orch-daemon/src/senders/tx.rs b/cw-orch-daemon/src/senders/tx.rs new file mode 100644 index 000000000..619d78ff9 --- /dev/null +++ b/cw-orch-daemon/src/senders/tx.rs @@ -0,0 +1,38 @@ +use cosmrs::{tx::Msg, AccountId, Any}; +use cosmwasm_std::Addr; + +use crate::CosmTxResponse; + +use super::query::QuerySender; + +pub trait TxSender: QuerySender { + /// Returns the `AccountId` of the sender that commits the transaction. + fn account_id(&self) -> AccountId; + + /// Commit a proto `Any` message to the chain using this sender. + fn commit_tx_any( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> impl std::future::Future> + Send; + + /// Get the address of the sender. + fn address(&self) -> Addr { + Addr::unchecked(self.account_id().to_string()) + } + + /// Commit a transaction to the chain using this sender. + fn commit_tx( + &self, + msgs: Vec, + memo: Option<&str>, + ) -> impl std::future::Future> + Send { + let msgs = msgs + .into_iter() + .map(Msg::into_any) + .collect::, _>>() + .unwrap(); + + self.commit_tx_any(msgs, memo) + } +} diff --git a/cw-orch-daemon/src/service/channel.rs b/cw-orch-daemon/src/service/channel.rs new file mode 100644 index 000000000..6e2c794ee --- /dev/null +++ b/cw-orch-daemon/src/service/channel.rs @@ -0,0 +1,78 @@ +use std::{ + cell::RefCell, + error::Error, + future::{ready, Future, Ready}, + pin::Pin, + rc::Rc, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use cw_orch_core::environment::ChainInfoOwned; +use tonic::{ + body::BoxBody, + client::GrpcService, + transport::{channel, Channel, Endpoint}, + Request, +}; +use tower::{ + reconnect::Reconnect, + retry::{Policy, Retry, RetryLayer}, + Layer, MakeService, Service, ServiceBuilder, ServiceExt, +}; + +use crate::{DaemonError, GrpcChannel}; + +#[derive(Clone)] +pub struct DaemonChannel { + // Service that retries on failures + pub(crate) svs: Retry, +} + +impl DaemonChannel { + pub fn new(channel: Channel) -> Self { + // Create the Reconnect layer with the factory + let retry_policy = MyRetryPolicy { + max_retries: 3, // Maximum number of retries + backoff: Duration::from_secs(1), // Backoff duration + }; + let retry_layer = RetryLayer::new(retry_policy); + let a: Retry = + ServiceBuilder::new().layer(retry_layer).service(channel); + Self { svs: a } + } +} + +/// Retry policy that retries on all errors up to a maximum number of retries. +#[derive(Debug, Clone)] +pub struct MyRetryPolicy { + pub max_retries: usize, + pub backoff: Duration, +} + +impl Policy, http::Response, E> for MyRetryPolicy +where + E: Into>, +{ + type Future = Ready; + + fn retry( + &self, + _req: &http::Request, + result: Result<&http::Response, &E>, + ) -> Option { + if self.max_retries > 0 && result.is_err() { + Some(ready(MyRetryPolicy { + max_retries: self.max_retries - 1, + backoff: self.backoff, + })) + } else { + None + } + } + + fn clone_request(&self, req: &http::Request) -> Option> { + None + } +} diff --git a/cw-orch-daemon/src/service/factory.rs b/cw-orch-daemon/src/service/factory.rs new file mode 100644 index 000000000..9664609bc --- /dev/null +++ b/cw-orch-daemon/src/service/factory.rs @@ -0,0 +1,76 @@ +use std::{ + cell::RefCell, + error::Error, + future::{ready, Future, Ready}, + pin::Pin, + rc::Rc, + sync::Arc, + task::{Context, Poll}, + time::Duration, +}; + +use cw_orch_core::environment::{ChainInfoOwned, ChainState}; +use tonic::{ + body::BoxBody, + client::GrpcService, + transport::{channel, Channel, Endpoint}, + Request, +}; +use tower::{ + reconnect::Reconnect, + retry::{Policy, Retry, RetryLayer}, + Layer, MakeService, Service, ServiceBuilder, ServiceExt, +}; + +use crate::{DaemonBase, DaemonError, GrpcChannel}; + +use super::{ + channel::{DaemonChannel, MyRetryPolicy}, + get_channel_creator, NewChannelRequest, Request as ChannelRequest, +}; + +/// Daemon Service is a wrapper around the [`Reconnect`] layer that is owned by the thread. +pub type DaemonService = Reconnect, ()>; + +// pub trait DaemonServiceCreation { +// /// Create a new `Rc>>` service for interacting with chain. +// async fn new_service(&self, chain_info: &ChainInfoOwned) -> Result { + +// Ok(DaemonService::new(RefCell::new(Reconnect::new::(DaemonChannelFactory {}, ))); +// } + +// /// Get a new service for interacting with the chain. +// async fn channel(&self) -> Result<&mut Reconnect, DaemonError>; +// } + +// TODO: take different type than channel +impl Service for DaemonBase { + type Response = Retry; + type Error = Box; + type Future = Pin> + Send>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, request: ()) -> Self::Future { + let fut = async move { + // Create request to get a new channel and re-create the service. + // TODO: Add a check here to ensure we only try to reconnect # number of times. Otherwise we will never fail. + let channel_creator = get_channel_creator().await; + let (reply_tx, reply_rx) = tokio::sync::mpsc::channel(1); + + let request = NewChannelRequest { + request: ChannelRequest { + grpc_urls: self.state().chain_data.as_ref().grpc_urls.clone(), + chain_id: self.state().chain_data.as_ref().chain_id.clone(), + }, + reply_tx, + }; + let _ = channel_creator.send(request).await; + let channel = reply_rx.recv().await.unwrap(); + Ok(DaemonChannel::new(request)) + }; + Box::pin(fut) + } +} diff --git a/cw-orch-daemon/src/service/mod.rs b/cw-orch-daemon/src/service/mod.rs new file mode 100644 index 000000000..8f8c81ec9 --- /dev/null +++ b/cw-orch-daemon/src/service/mod.rs @@ -0,0 +1,63 @@ +mod channel; +mod factory; + +use channel::DaemonChannelFactory; +use http::Uri; +use std::sync::Arc; +use tokio::sync::{mpsc, OnceCell}; +use tokio::task; +use tonic::transport::{Channel, Endpoint}; +use tower::reconnect::Reconnect; + +use crate::GrpcChannel; + +static SERVICE_FACTORY: OnceCell>> = OnceCell::const_new(); + +#[derive(Debug)] +struct NewChannelRequest { + request: Request, + reply_tx: mpsc::Sender, +} + +#[derive(Debug, Clone)] +pub struct Request { + grpc_urls: Vec, + chain_id: String, +} + +pub struct Response {} + +#[derive(Debug)] +pub struct ChannelFactory {} + +impl ChannelFactory { + pub async fn create_channel( + &self, + request: Request, + ) -> Result { + // TODO: move channel creation here, find endpoint with best connection (lowest latency / highest rate-limit) + // And round-robin new connections + let channel = GrpcChannel::connect(&chain_info.grpc_urls, &chain_info.chain_id).await?; + Ok(Response) + } +} + +async fn get_channel_creator() -> Arc> { + SERVICE_FACTORY + .get_or_init(|| async { + let (tx, mut rx) = mpsc::channel::(32); + let service_creator = Arc::new(tx.clone()); + + task::spawn(async move { + let factory = ServiceFactory {}; + while let Some(request) = rx.recv().await { + let response = factory.create_channel(request).await; + let _ = request.reply_tx.send(response).await; + } + }); + + service_creator + }) + .await + .clone() +} diff --git a/cw-orch-daemon/src/state.rs b/cw-orch-daemon/src/state.rs index c3105d4e6..efcd6e5a6 100644 --- a/cw-orch-daemon/src/state.rs +++ b/cw-orch-daemon/src/state.rs @@ -1,5 +1,7 @@ use super::error::DaemonError; use crate::env::{default_state_folder, DaemonEnvVars}; +use crate::service::{DaemonChannel, DaemonChannelFactory, DaemonService, DaemonServiceCreation}; +use crate::GrpcChannel; use crate::{json_lock::JsonLockedState, networks::ChainKind}; use cosmwasm_std::Addr; @@ -14,6 +16,7 @@ use std::{ path::Path, sync::Mutex, }; +use tonic::transport::Channel; /// Global state to track which files are already open by other daemons from other threads /// This is necessary because File lock will allow same process to lock file how many times as process wants @@ -28,7 +31,7 @@ pub struct DaemonState { /// Deployment identifier pub deployment_id: String, /// Information about the chain - pub chain_data: ChainInfoOwned, + pub chain_data: Arc, /// Whether to write on every change of the state pub write_on_change: bool, } @@ -58,7 +61,7 @@ impl DaemonState { /// Attempts to connect to any of the provided gRPC endpoints. pub fn new( mut json_file_path: String, - chain_data: ChainInfoOwned, + chain_data: &Arc, deployment_id: String, read_only: bool, write_on_change: bool, @@ -115,7 +118,7 @@ impl DaemonState { Ok(DaemonState { json_state, deployment_id, - chain_data, + chain_data: chain_data.clone(), write_on_change, }) } diff --git a/cw-orch-daemon/src/sync/builder.rs b/cw-orch-daemon/src/sync/builder.rs index a1e5d58f6..da7824b00 100644 --- a/cw-orch-daemon/src/sync/builder.rs +++ b/cw-orch-daemon/src/sync/builder.rs @@ -1,68 +1,57 @@ -use crate::{ - sender::{Sender, SenderBuilder, SenderOptions}, - DaemonAsyncBuilder, -}; -use crate::{DaemonState, RUNTIME}; -use bitcoin::secp256k1::All; +use crate::senders::builder::SenderBuilder; + +use crate::{DaemonAsyncBuilder, DaemonBase, DaemonState, Wallet, RUNTIME}; use cw_orch_core::environment::ChainInfoOwned; -use super::{super::error::DaemonError, core::Daemon}; +use super::super::error::DaemonError; -#[derive(Clone, Default)] +#[derive(Clone)] /// Create [`Daemon`] through [`DaemonBuilder`] /// ## Example /// ```no_run /// use cw_orch_daemon::{networks, DaemonBuilder}; /// -/// let Daemon = DaemonBuilder::default() -/// .chain(networks::LOCAL_JUNO) +/// let Daemon = DaemonBuilder::new(networks::LOCAL_JUNO) /// .deployment_id("v0.1.0") /// .build() /// .unwrap(); /// ``` pub struct DaemonBuilder { // # Required - pub(crate) chain: Option, + pub(crate) chain: ChainInfoOwned, + // # Optional pub(crate) handle: Option, pub(crate) deployment_id: Option, - pub(crate) overwrite_grpc_url: Option, - pub(crate) gas_denom: Option, - pub(crate) gas_fee: Option, pub(crate) state_path: Option, /// State from rebuild or existing daemon pub(crate) state: Option, pub(crate) write_on_change: Option, - /* Sender Options */ - /// Wallet sender - pub(crate) sender: Option>, - /// Specify Daemon Sender Options - pub(crate) sender_options: SenderOptions, + pub(crate) mnemonic: Option, } impl DaemonBuilder { - /// Set the chain the Daemon will connect to - pub fn chain(&mut self, chain: impl Into) -> &mut Self { - self.chain = Some(chain.into()); - self - } - - /// Set the deployment id to use for the Daemon interactions - /// Defaults to `default` - pub fn deployment_id(&mut self, deployment_id: impl Into) -> &mut Self { - self.deployment_id = Some(deployment_id.into()); - self + pub fn new(chain: impl Into) -> Self { + Self { + chain: chain.into(), + handle: None, + deployment_id: None, + state_path: None, + state: None, + write_on_change: None, + mnemonic: None, + } } /// Set a custom tokio runtime handle to use for the Daemon /// /// ## Example /// ```no_run - /// use cw_orch_daemon::Daemon; + /// use cw_orch_daemon::{Daemon, networks}; /// use tokio::runtime::Runtime; /// let rt = Runtime::new().unwrap(); - /// let Daemon = Daemon::builder() + /// let Daemon = Daemon::builder(networks::LOCAL_JUNO) /// .handle(rt.handle()) /// // ... /// .build() @@ -73,40 +62,22 @@ impl DaemonBuilder { self } - /// Set the mnemonic to use with this chain. - pub fn mnemonic(&mut self, mnemonic: impl ToString) -> &mut Self { - self.sender = Some(SenderBuilder::Mnemonic(mnemonic.to_string())); - self - } - - /// Specifies a sender to use with this chain - /// This will be used in priority when set on the builder - pub fn sender(&mut self, wallet: Sender) -> &mut Self { - self.sender = Some(SenderBuilder::Sender(wallet)); - self - } - - /// Specifies wether authz should be used with this daemon - pub fn authz_granter(&mut self, granter: impl ToString) -> &mut Self { - self.sender_options.set_authz_granter(granter.to_string()); - self - } - - /// Specifies wether feegrant should be used with this daemon - pub fn fee_granter(&mut self, granter: impl ToString) -> &mut Self { - self.sender_options.set_fee_granter(granter.to_string()); + /// Set the deployment id to use for the Daemon interactions + /// Defaults to `default` + pub fn deployment_id(&mut self, deployment_id: impl Into) -> &mut Self { + self.deployment_id = Some(deployment_id.into()); self } - /// Specifies the hd_index of the daemon sender - pub fn hd_index(&mut self, index: u32) -> &mut Self { - self.sender_options.hd_index = Some(index); + /// Overwrites the grpc_url used to interact with the chain + pub fn grpc_url(&mut self, url: impl Into) -> &mut Self { + self.chain.grpc_urls = vec![url.into()]; self } - /// Overwrites the grpc_url used to interact with the chain - pub fn grpc_url(&mut self, url: &str) -> &mut Self { - self.overwrite_grpc_url = Some(url.to_string()); + /// Set the mnemonic used for the default Cosmos wallet + pub fn mnemonic(&mut self, mnemonic: impl Into) -> &mut Self { + self.mnemonic = Some(mnemonic.into()); self } @@ -114,9 +85,14 @@ impl DaemonBuilder { /// Behavior : /// - If no gas denom is provided, the first gas denom specified in the `self.chain` is used /// - If no gas fee is provided, the first gas fee specified in the self.chain is used - pub fn gas(&mut self, gas_denom: Option<&str>, gas_fee: Option) -> &mut Self { - self.gas_denom = gas_denom.map(ToString::to_string); - self.gas_fee = gas_fee.map(Into::into); + pub fn gas(&mut self, gas_denom: Option<&str>, gas_price: Option) -> &mut Self { + if let Some(denom) = gas_denom { + self.chain.gas_denom = denom.to_string() + } + if let Some(price) = gas_price { + self.chain.gas_price = price; + } + self } @@ -136,53 +112,54 @@ impl DaemonBuilder { self } - /// Specifies path to the daemon state file - /// Defaults to env variable. - /// - /// Variable: STATE_FILE_ENV_NAME. - #[allow(unused)] - pub(crate) fn state_path(&mut self, path: impl ToString) -> &mut Self { - self.state_path = Some(path.to_string()); + /// Overwrite the chain info + pub fn chain(&mut self, chain: impl Into) -> &mut Self { + self.chain = chain.into(); self } - /// Build a Daemon - pub fn build(&self) -> Result { + /// Build a Daemon with the default [`Wallet`] implementation. + pub fn build(&self) -> Result, DaemonError> { let rt_handle = self .handle .clone() .unwrap_or_else(|| RUNTIME.handle().clone()); - let mut chain = self - .chain - .clone() - .ok_or(DaemonError::BuilderMissing("chain information".into()))?; - - // Override gas fee - overwrite_fee(&mut chain, self.gas_denom.clone(), self.gas_fee); - // Override grpc_url - overwrite_grpc_url(&mut chain, self.overwrite_grpc_url.clone()); - - let mut builder = self.clone(); - builder.chain = Some(chain); + let builder = self.clone(); // build the underlying daemon let daemon = rt_handle.block_on(DaemonAsyncBuilder::from(builder).build())?; - Ok(Daemon { rt_handle, daemon }) + Ok(DaemonBase { rt_handle, daemon }) } -} -fn overwrite_fee(chain: &mut ChainInfoOwned, denom: Option, amount: Option) { - if let Some(denom) = denom { - chain.gas_denom = denom.to_string() + /// Build a daemon + pub fn build_sender( + &self, + sender_options: T, + ) -> Result, DaemonError> { + let rt_handle = self + .handle + .clone() + .unwrap_or_else(|| RUNTIME.handle().clone()); + + let builder = self.clone(); + + // build the underlying daemon + let daemon = + rt_handle.block_on(DaemonAsyncBuilder::from(builder).build_sender(sender_options))?; + + Ok(DaemonBase { rt_handle, daemon }) } - chain.gas_price = amount.unwrap_or(chain.gas_price); -} -fn overwrite_grpc_url(chain: &mut ChainInfoOwned, grpc_url: Option) { - if let Some(grpc_url) = grpc_url { - chain.grpc_urls = vec![grpc_url.to_string()] + /// Specifies path to the daemon state file + /// Defaults to env variable. + /// + /// Variable: STATE_FILE_ENV_NAME. + #[allow(unused)] + pub(crate) fn state_path(&mut self, path: impl ToString) -> &mut Self { + self.state_path = Some(path.to_string()); + self } } @@ -191,7 +168,7 @@ mod test { use cw_orch_core::environment::TxHandler; use cw_orch_networks::networks::OSMOSIS_1; - use crate::DaemonBuilder; + use crate::{DaemonBase, DaemonBuilder, Wallet}; pub const DUMMY_MNEMONIC:&str = "chapter wrist alcohol shine angry noise mercy simple rebel recycle vehicle wrap morning giraffe lazy outdoor noise blood ginger sort reunion boss crowd dutch"; #[test] @@ -199,16 +176,15 @@ mod test { fn grpc_override() { let mut chain = OSMOSIS_1; chain.grpc_urls = &[]; - let daemon = DaemonBuilder::default() - .chain(chain) + let daemon = DaemonBuilder::new(chain) .mnemonic(DUMMY_MNEMONIC) .grpc_url(OSMOSIS_1.grpc_urls[0]) .build() .unwrap(); - assert_eq!(daemon.daemon.sender.chain_info.grpc_urls.len(), 1); + assert_eq!(daemon.daemon.sender().chain_info.grpc_urls.len(), 1); assert_eq!( - daemon.daemon.sender.chain_info.grpc_urls[0], + daemon.daemon.sender().chain_info.grpc_urls[0], OSMOSIS_1.grpc_urls[0].to_string(), ); } @@ -217,29 +193,30 @@ mod test { #[serial_test::serial] fn fee_amount_override() { let fee_amount = 1.3238763; - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .gas(None, Some(fee_amount)) .build() .unwrap(); - println!("chain {:?}", daemon.daemon.sender.chain_info); + println!("chain {:?}", daemon.daemon.sender().chain_info); - assert_eq!(daemon.daemon.sender.chain_info.gas_price, fee_amount); + assert_eq!(daemon.daemon.sender().chain_info.gas_price, fee_amount); } #[test] #[serial_test::serial] fn fee_denom_override() { let token = "my_token"; - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .gas(Some(token), None) .build() .unwrap(); - assert_eq!(daemon.daemon.sender.chain_info.gas_denom, token.to_string()); + assert_eq!( + daemon.daemon.sender().chain_info.gas_denom, + token.to_string() + ); } #[test] @@ -247,32 +224,35 @@ mod test { fn fee_override() { let fee_amount = 1.3238763; let token = "my_token"; - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .gas(Some(token), Some(fee_amount)) .build() .unwrap(); - assert_eq!(daemon.daemon.sender.chain_info.gas_denom, token.to_string()); + assert_eq!( + daemon.daemon.sender().chain_info.gas_denom, + token.to_string() + ); - assert_eq!(daemon.daemon.sender.chain_info.gas_price, fee_amount); + assert_eq!(daemon.daemon.sender().chain_info.gas_price, fee_amount); } #[test] #[serial_test::serial] fn hd_index_re_generates_sender() -> anyhow::Result<()> { - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); - let indexed_daemon = daemon.rebuild().hd_index(56).build().unwrap(); + let indexed_daemon: DaemonBase = daemon + .rebuild() + .build_sender(daemon.sender().options().hd_index(56))?; assert_ne!( - daemon.sender().to_string(), - indexed_daemon.sender().to_string() + daemon.sender_addr(), + indexed_daemon.sender_addr().to_string() ); Ok(()) diff --git a/cw-orch-daemon/src/sync/core.rs b/cw-orch-daemon/src/sync/core.rs index 239807a7d..bcbe1d011 100644 --- a/cw-orch-daemon/src/sync/core.rs +++ b/cw-orch-daemon/src/sync/core.rs @@ -1,89 +1,142 @@ -use std::fmt::Debug; +use std::{fmt::Debug, ops::DerefMut}; -use super::super::{sender::Wallet, DaemonAsync}; +use super::super::senders::Wallet; use crate::{ - queriers::{Bank, CosmWasm, Node}, - CosmTxResponse, DaemonBuilder, DaemonError, DaemonState, + queriers::{Bank, CosmWasmBase, Node}, + senders::{builder::SenderBuilder, query::QuerySender}, + CosmTxResponse, DaemonAsyncBase, DaemonBuilder, DaemonError, DaemonState, }; use cosmwasm_std::{Addr, Coin}; use cw_orch_core::{ contract::{interface_traits::Uploadable, WasmPath}, - environment::{ChainState, DefaultQueriers, QueryHandler, TxHandler}, + environment::{ChainInfoOwned, ChainState, DefaultQueriers, QueryHandler, TxHandler}, }; use cw_orch_traits::stargate::Stargate; use serde::Serialize; use tokio::runtime::Handle; use tonic::transport::Channel; +use crate::senders::tx::TxSender; + +pub type Daemon = DaemonBase; + #[derive(Clone)] /** - Represents a blockchain node. - Is constructed with the [DaemonBuilder]. +Represents a blockchain node. +Is constructed with the [DaemonBuilder]. - ## Usage +## Usage - ```rust,no_run - use cw_orch_daemon::{Daemon, networks}; - use tokio::runtime::Runtime; +```rust,no_run +use cw_orch_daemon::{Daemon, networks}; +use tokio::runtime::Runtime; - let rt = Runtime::new().unwrap(); - let daemon: Daemon = Daemon::builder() - .chain(networks::JUNO_1) - .build() - .unwrap(); - ``` - ## Environment Execution +let rt = Runtime::new().unwrap(); +let daemon: Daemon = Daemon::builder(networks::JUNO_1) + .build() + .unwrap(); +``` +## Environment Execution - The Daemon implements [`TxHandler`] which allows you to perform transactions on the chain. +The Daemon implements [`TxHandler`] which allows you to perform transactions on the chain. - ## Querying +## Querying - Different Cosmos SDK modules can be queried through the daemon by calling the [`Daemon.query_client`] method with a specific querier. - See [Querier](crate::queriers) for examples. +Different Cosmos SDK modules can be queried through the daemon by calling the [`Daemon.query_client`] method with a specific querier. +See [Querier](crate::queriers) for examples. */ -pub struct Daemon { - pub daemon: DaemonAsync, +pub struct DaemonBase { + pub(crate) daemon: DaemonAsyncBase, /// Runtime handle to execute async tasks pub rt_handle: Handle, } -impl Daemon { +impl DaemonBase { /// Get the daemon builder - pub fn builder() -> DaemonBuilder { - DaemonBuilder::default() + pub fn builder(chain: impl Into) -> DaemonBuilder { + DaemonBuilder::new(chain) + } + + /// Get the mutable Sender object + pub fn sender_mut(&mut self) -> &mut Sender { + self.daemon.sender_mut() + } + + /// Get the service to interact with a daemon + pub fn service(&self) -> Result { + self.rt_handle.block_on(self.daemon.service()) } /// Get the channel configured for this Daemon - pub fn channel(&self) -> Channel { - self.daemon.sender.grpc_channel.clone() + pub fn sender(&self) -> &Sender { + self.daemon.sender() + } + + /// Set the Sender for use with this Daemon + /// The sender will be configured with the chain's data. + pub fn new_sender( + self, + sender_options: T, + ) -> DaemonBase<::Sender> { + let new_daemon = self + .rt_handle + .block_on(self.daemon.new_sender(sender_options)); + DaemonBase { + daemon: new_daemon, + rt_handle: self.rt_handle.clone(), + } + } + + /// Flushes all the state related to the current chain + /// Only works on Local networks + pub fn flush_state(&mut self) -> Result<(), DaemonError> { + self.daemon.flush_state() } + /// Return the chain info for this daemon + pub fn chain_info(&self) -> &ChainInfoOwned { + self.daemon.chain_info() + } +} + +impl DaemonBase { /// Get the channel configured for this Daemon - pub fn wallet(&self) -> Wallet { - self.daemon.sender.clone() + pub fn channel(&self) -> Channel { + self.daemon.sender().channel() } /// Returns a new [`DaemonBuilder`] with the current configuration. + /// **Does not copy the `Sender`** /// Does not consume the original [`Daemon`]. pub fn rebuild(&self) -> DaemonBuilder { - let mut builder = DaemonBuilder { + DaemonBuilder { state: Some(self.state()), - ..Default::default() - }; - builder - .chain(self.daemon.sender.chain_info.clone()) - .sender((*self.daemon.sender).clone()); - builder + chain: self.daemon.chain_info().clone(), + deployment_id: Some(self.daemon.state.deployment_id.clone()), + state_path: None, + write_on_change: None, + handle: Some(self.rt_handle.clone()), + mnemonic: None, + } } +} - /// Flushes all the state related to the current chain - /// Only works on Local networks - pub fn flush_state(&mut self) -> Result<(), DaemonError> { - self.daemon.flush_state() +// Helpers for Daemon with [`Wallet`] sender. +impl Daemon { + /// Specifies wether authz should be used with this daemon + pub fn authz_granter(&mut self, granter: impl ToString) -> &mut Self { + self.sender_mut().set_authz_granter(granter.to_string()); + self + } + + /// Specifies wether feegrant should be used with this daemon + pub fn fee_granter(&mut self, granter: impl ToString) -> &mut Self { + self.sender_mut().set_fee_granter(granter.to_string()); + self } } -impl ChainState for Daemon { +impl ChainState for DaemonBase { type Out = DaemonState; fn state(&self) -> Self::Out { @@ -92,18 +145,21 @@ impl ChainState for Daemon { } // Execute on the real chain, returns tx response -impl TxHandler for Daemon { +impl TxHandler for DaemonBase { type Response = CosmTxResponse; type Error = DaemonError; type ContractSource = WasmPath; - type Sender = Wallet; + type Sender = Sender; - fn sender(&self) -> Addr { - self.daemon.sender.address().unwrap() + fn sender_addr(&self) -> Addr { + self.daemon.sender_addr() } + /// Overwrite the sender manually, could result in unexpected behavior. + /// Use native [`Daemon::new_sender`] instead! fn set_sender(&mut self, sender: Self::Sender) { - self.daemon.sender = sender + let mut daemon_sender = self.daemon.sender_mut(); + (*daemon_sender.deref_mut()) = sender; } fn upload(&self, uploadable: &T) -> Result { @@ -162,27 +218,29 @@ impl TxHandler for Daemon { } } -impl Stargate for Daemon { +impl Stargate for DaemonBase { fn commit_any( &self, msgs: Vec, memo: Option<&str>, ) -> Result { - self.rt_handle.block_on( - self.wallet().commit_tx_any( - msgs.iter() - .map(|msg| cosmrs::Any { - type_url: msg.type_url.clone(), - value: msg.value.clone(), - }) - .collect(), - memo, - ), - ) + self.rt_handle + .block_on( + self.sender().commit_tx_any( + msgs.iter() + .map(|msg| cosmrs::Any { + type_url: msg.type_url.clone(), + value: msg.value.clone(), + }) + .collect(), + memo, + ), + ) + .map_err(Into::into) } } -impl QueryHandler for Daemon { +impl QueryHandler for DaemonBase { type Error = DaemonError; fn wait_blocks(&self, amount: u64) -> Result<(), DaemonError> { @@ -204,8 +262,8 @@ impl QueryHandler for Daemon { } } -impl DefaultQueriers for Daemon { +impl DefaultQueriers for DaemonBase { type Bank = Bank; - type Wasm = CosmWasm; + type Wasm = CosmWasmBase; type Node = Node; } diff --git a/cw-orch-daemon/src/tx_broadcaster.rs b/cw-orch-daemon/src/tx_broadcaster.rs index effc6f335..9632862f9 100644 --- a/cw-orch-daemon/src/tx_broadcaster.rs +++ b/cw-orch-daemon/src/tx_broadcaster.rs @@ -1,8 +1,7 @@ -use bitcoin::secp256k1::All; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use cw_orch_core::log::transaction_target; -use crate::{queriers::Node, sender::Sender, CosmTxResponse, DaemonError, TxBuilder}; +use crate::{queriers::Node, CosmTxResponse, DaemonError, TxBuilder, Wallet}; pub type StrategyAction = fn(&mut TxBuilder, &Result) -> Result<(), DaemonError>; @@ -68,7 +67,7 @@ impl TxBroadcaster { pub async fn broadcast( mut self, mut tx_builder: TxBuilder, - wallet: &Sender, + wallet: &Wallet, ) -> Result { let mut tx_retry = true; @@ -123,7 +122,7 @@ fn strategy_condition_met( async fn broadcast_helper( tx_builder: &mut TxBuilder, - wallet: &Sender, + wallet: &Wallet, ) -> Result { let tx = tx_builder.build(wallet).await?; let tx_response = wallet.broadcast_tx(tx).await?; @@ -149,7 +148,7 @@ pub(crate) fn assert_broadcast_code_response( } /// Tx Responses with a non 0 code, should also error with the raw loq -pub(crate) fn assert_broadcast_code_cosm_response( +pub fn assert_broadcast_code_cosm_response( tx_response: CosmTxResponse, ) -> Result { // if tx result != 0 then the tx failed, so we return an error diff --git a/cw-orch-daemon/src/tx_builder.rs b/cw-orch-daemon/src/tx_builder.rs index 140760c32..4aeee68a7 100644 --- a/cw-orch-daemon/src/tx_builder.rs +++ b/cw-orch-daemon/src/tx_builder.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use bitcoin::secp256k1::All; use cosmrs::tx::{ModeInfo, SignMode}; use cosmrs::AccountId; use cosmrs::{ @@ -11,9 +10,9 @@ use cosmrs::{ }; use cw_orch_core::log::transaction_target; -use crate::sender::SenderOptions; +use crate::Wallet; -use super::{sender::Sender, DaemonError}; +use super::DaemonError; /// Struct used to build a raw transaction and broadcast it with a sender. #[derive(Clone, Debug)] @@ -62,23 +61,20 @@ impl TxBuilder { ) } - pub(crate) fn build_fee( + pub fn build_fee( amount: impl Into, denom: &str, gas_limit: u64, - sender_options: SenderOptions, + fee_granter: Option, ) -> Result { let fee = Coin::new(amount.into(), denom).unwrap(); let mut fee = Fee::from_amount_and_gas(fee, gas_limit); - fee.granter = sender_options - .fee_granter - .map(|g| AccountId::from_str(&g)) - .transpose()?; + fee.granter = fee_granter.map(|g| AccountId::from_str(&g)).transpose()?; Ok(fee) } /// Simulates the transaction and returns the necessary gas fee returned by the simulation on a node - pub async fn simulate(&self, wallet: &Sender) -> Result { + pub async fn simulate(&self, wallet: &Wallet) -> Result { // get the account number of the wallet let BaseAccount { account_number, @@ -96,7 +92,7 @@ impl TxBuilder { /// Builds the raw tx with a given body and fee and signs it. /// Sets the TxBuilder's gas limit to its simulated amount for later use. - pub async fn build(&mut self, wallet: &Sender) -> Result { + pub async fn build(&mut self, wallet: &Wallet) -> Result { // get the account number of the wallet let BaseAccount { account_number, @@ -138,7 +134,7 @@ impl TxBuilder { tx_fee, &wallet.get_fee_token(), gas_limit, - wallet.options.clone(), + wallet.options.fee_granter.clone(), )?; log::debug!( diff --git a/cw-orch-daemon/tests/authz.rs b/cw-orch-daemon/tests/authz.rs index c2a662796..aa367ebe3 100644 --- a/cw-orch-daemon/tests/authz.rs +++ b/cw-orch-daemon/tests/authz.rs @@ -15,7 +15,7 @@ mod tests { use cosmwasm_std::coins; use cw_orch_core::environment::QuerierGetter; use cw_orch_core::environment::{BankQuerier, DefaultQueriers, QueryHandler, TxHandler}; - use cw_orch_daemon::{queriers::Authz, Daemon}; + use cw_orch_daemon::{queriers::Authz, senders::CosmosOptions, Daemon}; use cw_orch_networks::networks::LOCAL_JUNO; use cw_orch_traits::Stargate; use prost::Message; @@ -28,23 +28,22 @@ mod tests { fn authz() -> anyhow::Result<()> { use cw_orch_networks::networks; - let daemon = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let daemon = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); - let sender = daemon.sender().to_string(); + let sender = daemon.sender_addr().to_string(); - let second_daemon = daemon + let second_daemon: Daemon = daemon .rebuild() - .authz_granter(sender.clone()) - .mnemonic(SECOND_MNEMONIC) - .build() + .build_sender( + CosmosOptions::default() + .mnemonic(SECOND_MNEMONIC) + .authz_granter(sender.clone()), + ) .unwrap(); let runtime = daemon.rt_handle.clone(); - let grantee = second_daemon.sender().to_string(); + let grantee = second_daemon.sender_addr().to_string(); let current_timestamp = daemon.block_info()?.time; @@ -63,6 +62,7 @@ mod tests { authorization: Some(authorization.clone()), expiration: Some(expiration.clone()), }; + // We start by granting authz to an account daemon.commit_any::( vec![Any { @@ -118,26 +118,24 @@ mod tests { // The we send some funds to the account runtime.block_on( daemon - .daemon - .sender - .bank_send(&grantee, coins(1_000_000, LOCAL_JUNO.gas_denom)), + .sender() + .bank_send(&grantee, coins(100_000, LOCAL_JUNO.gas_denom)), )?; // And send a large amount of tokens on their behalf runtime.block_on( second_daemon - .daemon - .sender - .bank_send(&grantee, coins(5_000_000, LOCAL_JUNO.gas_denom)), + .sender() + .bank_send(&grantee, coins(500_000, LOCAL_JUNO.gas_denom)), )?; - // the balance of the grantee whould be 6_000_000 or close + // the balance of the grantee whould be 600_000 or close let grantee_balance = daemon .bank_querier() .balance(grantee.clone(), Some(LOCAL_JUNO.gas_denom.to_string()))?; - assert_eq!(grantee_balance.first().unwrap().amount.u128(), 6_000_000); + assert_eq!(grantee_balance.first().unwrap().amount.u128(), 600_000); Ok(()) } diff --git a/cw-orch-daemon/tests/daemon_helpers.rs b/cw-orch-daemon/tests/daemon_helpers.rs index 3f4bc6dc9..e4f383d20 100644 --- a/cw-orch-daemon/tests/daemon_helpers.rs +++ b/cw-orch-daemon/tests/daemon_helpers.rs @@ -20,14 +20,11 @@ mod tests { fn helper_traits() { use cw_orch_networks::networks; - let mut daemon = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let mut daemon = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); daemon.flush_state().unwrap(); - let sender = daemon.sender(); + let sender = daemon.sender_addr(); let contract = mock_contract::MockContract::new( format!("test:mock_contract:{}", Id::new()), @@ -118,12 +115,9 @@ mod tests { fn cw_orch_interface_traits() { use cw_orch_networks::networks; - let daemon = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let daemon = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); - let sender = daemon.sender(); + let sender = daemon.sender_addr(); let contract = mock_contract::MockContract::new( format!("test:mock_contract:{}", Id::new()), diff --git a/cw-orch-daemon/tests/daemon_state.rs b/cw-orch-daemon/tests/daemon_state.rs index a59b34447..421b23120 100644 --- a/cw-orch-daemon/tests/daemon_state.rs +++ b/cw-orch-daemon/tests/daemon_state.rs @@ -5,7 +5,7 @@ use cw_orch_daemon::{ env::STATE_FILE_ENV_NAME, json_lock::JsonLockedState, networks::{JUNO_1, OSMOSIS_1}, - DaemonBuilder, DaemonError, DaemonStateFile, + Daemon, DaemonBuilder, DaemonError, DaemonStateFile, }; pub const DUMMY_MNEMONIC:&str = "chapter wrist alcohol shine angry noise mercy simple rebel recycle vehicle wrap morning giraffe lazy outdoor noise blood ginger sort reunion boss crowd dutch"; @@ -16,8 +16,7 @@ const TEST2_STATE_FILE: &str = "./tests/test2.json"; #[serial_test::serial] fn simultaneous_read() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); @@ -60,8 +59,7 @@ fn simultaneous_read() { #[serial_test::serial] fn simultaneous_write() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); @@ -101,16 +99,17 @@ fn simultaneous_write() { #[serial_test::serial] fn simultaneous_write_rebuilt() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); + let options = daemon.sender().options().clone(); + let mut handles = vec![]; // Note this one has lower iterations since rebuild is pretty long process for i in 0..10 { - let daemon = daemon.rebuild().build().unwrap(); + let daemon: Daemon = daemon.rebuild().build_sender(options.clone()).unwrap(); let mut daemon_state = daemon.state(); let handle = std::thread::spawn(move || { if let DaemonStateFile::FullAccess { json_file_state } = &daemon_state.json_state { @@ -144,14 +143,12 @@ fn simultaneous_write_rebuilt() { #[serial_test::serial] fn error_when_another_daemon_holds_it() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let _daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let _daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); - let daemon_res = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon_res = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build(); @@ -166,16 +163,14 @@ fn error_when_another_daemon_holds_it() { #[serial_test::serial] fn does_not_error_when_previous_daemon_dropped_state() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); drop(daemon); - let daemon_res = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon_res = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build(); @@ -187,16 +182,14 @@ fn does_not_error_when_previous_daemon_dropped_state() { #[serial_test::serial] fn does_not_error_when_using_different_files() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let _daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + let _daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); // Different file std::env::set_var(STATE_FILE_ENV_NAME, TEST2_STATE_FILE); - let daemon_res = DaemonBuilder::default() - .chain(OSMOSIS_1) + let daemon_res = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build(); @@ -208,14 +201,13 @@ fn does_not_error_when_using_different_files() { #[serial_test::serial] fn reuse_same_state_multichain() { std::env::set_var(STATE_FILE_ENV_NAME, TEST_STATE_FILE); - let daemon = DaemonBuilder::default() - .chain(OSMOSIS_1) + + let daemon = DaemonBuilder::new(OSMOSIS_1) .mnemonic(DUMMY_MNEMONIC) .build() .unwrap(); - let daemon_res = DaemonBuilder::default() - .chain(JUNO_1) + let daemon_res = DaemonBuilder::new(JUNO_1) .state(daemon.state()) .mnemonic(DUMMY_MNEMONIC) .build(); diff --git a/cw-orch-daemon/tests/index.rs b/cw-orch-daemon/tests/index.rs index fb5264977..42f8f4c8d 100644 --- a/cw-orch-daemon/tests/index.rs +++ b/cw-orch-daemon/tests/index.rs @@ -3,22 +3,22 @@ mod common; mod tests { use cw_orch_core::environment::TxHandler; - use cw_orch_daemon::Daemon; + use cw_orch_daemon::{senders::CosmosOptions, Daemon}; #[test] #[serial_test::serial] fn mnemonic_index() -> anyhow::Result<()> { use cw_orch_networks::networks; - let daemon = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let daemon = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); - let daemon_sender = daemon.sender().to_string(); - let indexed_daemon = daemon.rebuild().hd_index(56).build().unwrap(); + let daemon_sender = daemon.sender_addr().to_string(); + let indexed_daemon: Daemon = daemon + .rebuild() + .build_sender(CosmosOptions::default().hd_index(56)) + .unwrap(); - assert_ne!(daemon_sender, indexed_daemon.sender().to_string()); + assert_ne!(daemon_sender, indexed_daemon.sender_addr().to_string()); Ok(()) } diff --git a/cw-orch-daemon/tests/instantiate2.rs b/cw-orch-daemon/tests/instantiate2.rs index 72c9acbc1..2bdbccaac 100644 --- a/cw-orch-daemon/tests/instantiate2.rs +++ b/cw-orch-daemon/tests/instantiate2.rs @@ -15,10 +15,7 @@ pub mod test { #[test] #[serial_test::serial] fn instantiate2() -> anyhow::Result<()> { - let app = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let app = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); let salt = Binary(vec![12, 89, 156, 63]); let mock_contract = MockContract::new("mock-contract", app.clone()); diff --git a/cw-orch-daemon/tests/querier.rs b/cw-orch-daemon/tests/querier.rs index a5d94d813..4c917950f 100644 --- a/cw-orch-daemon/tests/querier.rs +++ b/cw-orch-daemon/tests/querier.rs @@ -4,7 +4,6 @@ mod common; mod queriers { use cw_orch_core::contract::interface_traits::*; - use cw_orch_core::environment::TxHandler; use cw_orch_daemon::{queriers::Bank, GrpcChannel}; use cw_orch_networks::networks; use mock_contract::InstantiateMsg; @@ -208,15 +207,13 @@ mod queriers { #[serial_test::serial] fn contract_info() { use crate::common::Id; + use cw_orch_daemon::TxSender; use cw_orch_networks::networks; let rt = Runtime::new().unwrap(); let channel = rt.block_on(build_channel()); let cosm_wasm = CosmWasm::new_async(channel); - let daemon = Daemon::builder() - .chain(networks::LOCAL_JUNO) - .build() - .unwrap(); + let daemon = Daemon::builder(networks::LOCAL_JUNO).build().unwrap(); let sender = daemon.sender(); @@ -228,7 +225,7 @@ mod queriers { contract.upload().unwrap(); contract - .instantiate(&InstantiateMsg {}, Some(&sender), None) + .instantiate(&InstantiateMsg {}, Some(&sender.address()), None) .unwrap(); let contract_address = contract.address().unwrap(); diff --git a/cw-orch-interchain/examples/doc_daemon.rs b/cw-orch-interchain/examples/doc_daemon.rs index dcf36fd2b..2f9b7eeb1 100644 --- a/cw-orch-interchain/examples/doc_daemon.rs +++ b/cw-orch-interchain/examples/doc_daemon.rs @@ -18,9 +18,8 @@ fn create_daemon_env() -> cw_orch::anyhow::Result<(Runtime, DaemonInterchainEnv) let _local_juno: Daemon = interchain.chain("testing")?; let _local_osmo: Daemon = interchain.chain("localosmosis")?; - let local_migaloo = DaemonBuilder::default() + let local_migaloo = DaemonBuilder::new(LOCAL_MIGALOO) .handle(rt.handle()) - .chain(LOCAL_MIGALOO) .build()?; interchain.add_daemons(vec![local_migaloo]); diff --git a/cw-orch-interchain/examples/timeout_packet.rs b/cw-orch-interchain/examples/timeout_packet.rs index 95bd394da..e6fabb712 100644 --- a/cw-orch-interchain/examples/timeout_packet.rs +++ b/cw-orch-interchain/examples/timeout_packet.rs @@ -6,11 +6,7 @@ use cosmos_sdk_proto::{ traits::{Message, Name}, Any, }; -use cw_orch::{ - environment::{QueryHandler, TxHandler}, - prelude::Stargate, - tokio::runtime::Runtime, -}; +use cw_orch::{environment::QueryHandler, prelude::*, tokio::runtime::Runtime}; use cw_orch_interchain_core::InterchainEnv; use cw_orch_interchain_daemon::ChannelCreator as _; use cw_orch_starship::Starship; @@ -49,8 +45,8 @@ fn main() -> cw_orch::anyhow::Result<()> { amount: "100_000".to_string(), denom: "ujuno".to_string(), }), - sender: juno.sender().to_string(), - receiver: stargaze.sender().to_string(), + sender: juno.sender_addr().to_string(), + receiver: stargaze.sender_addr().to_string(), timeout_height: Some(Height { revision_number: 1, revision_height: stargaze_height.height, diff --git a/cw-orch-interchain/tests/common/bank.rs b/cw-orch-interchain/tests/common/bank.rs index 6c1885b13..1c83e119e 100644 --- a/cw-orch-interchain/tests/common/bank.rs +++ b/cw-orch-interchain/tests/common/bank.rs @@ -37,6 +37,6 @@ impl BankModule for Daemon { funds: Vec, ) -> Result<::Response, ::Error> { self.rt_handle - .block_on(self.wallet().bank_send(recipient.as_ref(), funds)) + .block_on(self.sender().bank_send(recipient.as_ref(), funds)) } } diff --git a/cw-orch-interchain/tests/timeout_packet_mock.rs b/cw-orch-interchain/tests/timeout_packet_mock.rs index 81ed310ad..9daa58f53 100644 --- a/cw-orch-interchain/tests/timeout_packet_mock.rs +++ b/cw-orch-interchain/tests/timeout_packet_mock.rs @@ -29,12 +29,12 @@ fn timeout_packet_mock() -> cw_orch::anyhow::Result<()> { .interchain_channel .get_ordered_ports_from("juno-1")?; - juno.add_balance(juno.sender().to_string(), vec![coin(100_000, "ujuno")])?; + juno.add_balance(juno.sender_addr().to_string(), vec![coin(100_000, "ujuno")])?; let tx_resp = juno.app.borrow_mut().execute( - juno.sender(), + juno.sender_addr(), CosmosMsg::Ibc(IbcMsg::Transfer { channel_id: channel.0.channel.unwrap().to_string(), - to_address: stargaze.sender().to_string(), + to_address: stargaze.sender_addr().to_string(), amount: coin(100_000, "ujuno"), timeout: IbcTimeout::with_block(IbcTimeoutBlock { revision: 1, diff --git a/cw-orch/Cargo.toml b/cw-orch/Cargo.toml index e9693070c..dd5d5b4c8 100644 --- a/cw-orch/Cargo.toml +++ b/cw-orch/Cargo.toml @@ -46,7 +46,6 @@ default = [] # enable the optional dependencies daemon = [ "dep:tokio", - # "dep:tonic", "dep:cosmrs", "dep:cw-orch-daemon", "dep:cw-orch-networks", @@ -59,7 +58,6 @@ snapshot-testing = ["dep:insta", "dep:sanitize-filename"] cw-orch-contract-derive = { workspace = true } cw-orch-fns-derive = { workspace = true } - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Daemon deps diff --git a/cw-orch/examples/async_daemon.rs b/cw-orch/examples/async_daemon.rs index d8c6c8f45..48e5d9512 100644 --- a/cw-orch/examples/async_daemon.rs +++ b/cw-orch/examples/async_daemon.rs @@ -18,9 +18,7 @@ pub async fn main() -> anyhow::Result<()> { // We can now create a daemon. This daemon will be used to interact with the chain. // In the background, the `build` function uses the `TEST_MNEMONIC` variable, don't forget to set it ! - let daemon = DaemonAsync::builder() - // set the network to use - .chain(cw_orch::daemon::networks::UNI_6) + let daemon = DaemonAsync::builder(cw_orch::daemon::networks::UNI_6) // set the network to use .build() .await?; diff --git a/cw-orch/examples/complex_testnet_daemon.rs b/cw-orch/examples/complex_testnet_daemon.rs index 52229cc13..edd05baec 100644 --- a/cw-orch/examples/complex_testnet_daemon.rs +++ b/cw-orch/examples/complex_testnet_daemon.rs @@ -31,9 +31,7 @@ pub fn main() { // We can now create a daemon. This daemon will be used to interact with the chain. // In the background, the `build` function uses the `TEST_MNEMONIC` variable, don't forget to set it ! - let daemon = Daemon::builder() - // set the network to use - .chain(cw_orch::daemon::networks::UNI_6) + let daemon = Daemon::builder(cw_orch::daemon::networks::UNI_6) // set the network to use .build() .unwrap(); @@ -46,7 +44,7 @@ pub fn main() { let init_res = counter.instantiate( &InstantiateMsg { count: 0 }, - Some(&counter.environment().sender()), + Some(&counter.environment().sender_addr()), None, ); assert!(init_res.is_ok()); @@ -65,7 +63,7 @@ pub fn main() { let query_res = counter.get_count(); assert!(query_res.is_ok()); - let sender_addr = daemon.sender().to_string(); + let sender_addr = daemon.sender_addr().to_string(); // We create a denom daemon .commit_any::( @@ -105,8 +103,7 @@ pub fn main() { .rt_handle .block_on( daemon - .daemon - .sender + .sender() .bank_send(&contract_addr, coins(50_000, denom.clone())), ) .unwrap(); diff --git a/cw-orch/examples/injective.rs b/cw-orch/examples/injective.rs index 272ad66d1..232492d89 100644 --- a/cw-orch/examples/injective.rs +++ b/cw-orch/examples/injective.rs @@ -18,9 +18,7 @@ pub fn main() { env_logger::init(); // We can now create a daemon. This daemon will be used to interact with the chain. - let res = Daemon::builder() - // set the network to use - .chain(networks::INJECTIVE_888) + let res = Daemon::builder(networks::INJECTIVE_888) // set the network to use .mnemonic(TESTNET_MNEMONIC) .build(); @@ -35,7 +33,7 @@ pub fn main() { let init_res = counter.instantiate( &InstantiateMsg { count: 0 }, - Some(&counter.environment().sender()), + Some(&counter.environment().sender_addr()), None, ); assert!(init_res.is_ok()); diff --git a/cw-orch/examples/local_daemon.rs b/cw-orch/examples/local_daemon.rs index 7269627a9..166e307f4 100644 --- a/cw-orch/examples/local_daemon.rs +++ b/cw-orch/examples/local_daemon.rs @@ -19,9 +19,7 @@ pub fn main() { // ANCHOR: daemon_creation // We start by creating a daemon. This daemon will be used to interact with the chain. - let daemon = Daemon::builder() - // set the network to use - .chain(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter + let daemon = Daemon::builder(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter .build() .unwrap(); @@ -35,7 +33,7 @@ pub fn main() { let init_res = counter.instantiate( &InstantiateMsg { count: 0 }, - Some(&counter.environment().sender()), + Some(&counter.environment().sender_addr()), None, ); assert!(init_res.is_ok()); diff --git a/cw-orch/examples/queries/bank_query.rs b/cw-orch/examples/queries/bank_query.rs index 10e55cdce..894dd645c 100644 --- a/cw-orch/examples/queries/bank_query.rs +++ b/cw-orch/examples/queries/bank_query.rs @@ -5,8 +5,7 @@ use cw_orch::prelude::QuerierGetter; use cw_orch_daemon::queriers::Bank; pub fn main() { // We can now create a daemon. This daemon will be used to interact with the chain. - let daemon = Daemon::builder() - .chain(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter + let daemon = Daemon::builder(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter .build() .unwrap(); diff --git a/cw-orch/examples/queries/testnet_queries.rs b/cw-orch/examples/queries/testnet_queries.rs index 1ceafabe9..9c797c4ed 100644 --- a/cw-orch/examples/queries/testnet_queries.rs +++ b/cw-orch/examples/queries/testnet_queries.rs @@ -8,8 +8,7 @@ pub const TEST_MNEMONIC: &str="scare silent genuine cheese monitor industry item pub fn main() -> AnyResult<()> { // We start by creating a daemon. This daemon will be used to interact with the chain. - let daemon = Daemon::builder() - .chain(cw_orch::daemon::networks::JUNO_1) // chain parameter + let daemon = Daemon::builder(cw_orch::daemon::networks::JUNO_1) // chain parameter .mnemonic(TEST_MNEMONIC) .build()?; diff --git a/cw-orch/examples/testnet_daemon.rs b/cw-orch/examples/testnet_daemon.rs index a4641f665..efe2e1b3e 100644 --- a/cw-orch/examples/testnet_daemon.rs +++ b/cw-orch/examples/testnet_daemon.rs @@ -22,9 +22,7 @@ pub fn main() { // We can now create a daemon. This daemon will be used to interact with the chain. // In the background, the `build` function uses the `TEST_MNEMONIC` variable, don't forget to set it ! - let daemon = Daemon::builder() - // set the network to use - .chain(cw_orch::daemon::networks::UNI_6) + let daemon = Daemon::builder(cw_orch::daemon::networks::UNI_6) // set the network to use .build() .unwrap(); @@ -37,7 +35,7 @@ pub fn main() { let init_res = counter.instantiate( &InstantiateMsg { count: 0 }, - Some(&counter.environment().sender()), + Some(&counter.environment().sender_addr()), None, ); assert!(init_res.is_ok()); diff --git a/cw-orch/src/daemon.rs b/cw-orch/src/daemon.rs index 48b800ff9..247e06057 100644 --- a/cw-orch/src/daemon.rs +++ b/cw-orch/src/daemon.rs @@ -1,6 +1,4 @@ //! `Daemon` and `DaemonAsync` execution environments. //! //! The `Daemon` type is a synchronous wrapper around the `DaemonAsync` type and can be used as a contract execution environment. - -pub use cw_orch_daemon::sender::Wallet; pub use cw_orch_daemon::*; diff --git a/cw-orch/tests/entry_point_macro.rs b/cw-orch/tests/entry_point_macro.rs index e1b122ed3..7f5425e18 100644 --- a/cw-orch/tests/entry_point_macro.rs +++ b/cw-orch/tests/entry_point_macro.rs @@ -65,7 +65,7 @@ fn test_migrate() { contract.upload().unwrap(); contract - .instantiate(&InstantiateMsg {}, Some(&chain.sender()), None) + .instantiate(&InstantiateMsg {}, Some(&chain.sender_addr()), None) .unwrap(); contract diff --git a/cw-orch/tests/interface_macro.rs b/cw-orch/tests/interface_macro.rs index fdebef666..e4fe07235 100644 --- a/cw-orch/tests/interface_macro.rs +++ b/cw-orch/tests/interface_macro.rs @@ -95,7 +95,7 @@ fn test_migrate() { contract.upload().unwrap(); contract - .instantiate(&InstantiateMsg {}, Some(&chain.sender()), None) + .instantiate(&InstantiateMsg {}, Some(&chain.sender_addr()), None) .unwrap(); contract diff --git a/cw-orch/tests/interface_macro_generics.rs b/cw-orch/tests/interface_macro_generics.rs index a14d2bed0..e1a0cd270 100644 --- a/cw-orch/tests/interface_macro_generics.rs +++ b/cw-orch/tests/interface_macro_generics.rs @@ -86,7 +86,7 @@ fn test_migrate() { contract.upload().unwrap(); contract - .instantiate(&InstantiateMsg {}, Some(&chain.sender()), None) + .instantiate(&InstantiateMsg {}, Some(&chain.sender_addr()), None) .unwrap(); contract diff --git a/docs/src/integrations/daemon.md b/docs/src/integrations/daemon.md index 0aec1ebd1..99675bbbe 100644 --- a/docs/src/integrations/daemon.md +++ b/docs/src/integrations/daemon.md @@ -26,13 +26,10 @@ This simple script actually hides another parameter which is the `LOCAL_MNEMONIC When using multiple Daemons with the same state file, you should re-use a single Daemon State to avoid conflicts and panics: ```rust,ignore -let daemon1 = Daemon::builder() - .chain(OSMOSIS_1) - .build()?; +let daemon1 = Daemon::builder(OSMOSIS_1).build()?; // If you don't use the `state` method here, this will fail with: // State file already locked, use another state file, clone daemon which holds the lock, or use `state` method of Builder -let daemon2 = Daemon::builder() - .chain(JUNO_1) +let daemon2 = Daemon::builder(JUNO_1) .state(daemon1.state()) .build()?; ``` diff --git a/docs/src/interchain/integrations/daemon.md b/docs/src/interchain/integrations/daemon.md index 8c12fb70a..985508a73 100644 --- a/docs/src/interchain/integrations/daemon.md +++ b/docs/src/interchain/integrations/daemon.md @@ -41,9 +41,7 @@ where the argument of the `chain` method is the chain id of the chain you are in You can also add daemons manually to the `interchain` object: ```rust,ignore -let local_migaloo = DaemonBuilder::default() - .chain(LOCAL_MIGALOO) - .build()?; +let local_migaloo = DaemonBuilder::default(LOCAL_MIGALOO).build()?; interchain.add_daemons(vec![local_migaloo]); ``` diff --git a/docs/src/interchain/quick-start.md b/docs/src/interchain/quick-start.md index 0a60f9e2a..c07f9f77f 100644 --- a/docs/src/interchain/quick-start.md +++ b/docs/src/interchain/quick-start.md @@ -196,14 +196,12 @@ use cw_orch::tokio; let rt = tokio::runtime::Runtime::new().unwrap(); // We create the daemons ourselves to interact with actual running chains (testnet here) -let juno = Daemon::builder() - .chain(cw_orch::daemon::networks::UNI_6) +let juno = Daemon::builder(cw_orch::daemon::networks::UNI_6) .build() .unwrap(); // We create the daemons ourselves to interact with actual running chains (testnet here) -let osmosis = Daemon::builder() - .chain(cw_orch::daemon::networks::OSMO_5) +let osmosis = Daemon::builder(cw_orch::daemon::networks::OSMO_5) .build() .unwrap(); diff --git a/packages/clone-testing/src/core.rs b/packages/clone-testing/src/core.rs index 8da6420cd..cc65d12e8 100644 --- a/packages/clone-testing/src/core.rs +++ b/packages/clone-testing/src/core.rs @@ -240,7 +240,7 @@ impl TxHandler for CloneTesting { type ContractSource = Box>; type Sender = Addr; - fn sender(&self) -> Addr { + fn sender_addr(&self) -> Addr { self.sender.clone() } @@ -508,7 +508,7 @@ mod test { let rt = Runtime::new().unwrap(); let chain = CloneTesting::new(&rt, chain)?; - let sender = chain.sender(); + let sender = chain.sender_addr(); let recipient = &chain.init_account(); chain @@ -522,7 +522,7 @@ mod test { asserting("sender is correct") .that(&sender) - .is_equal_to(chain.sender()); + .is_equal_to(chain.sender_addr()); let init_res = chain.upload(&MockCw20).unwrap(); let code_id = (1 + LOCAL_RUST_CODE_OFFSET) as u64; diff --git a/packages/clone-testing/src/state.rs b/packages/clone-testing/src/state.rs index e4e2363b5..5fde618c2 100644 --- a/packages/clone-testing/src/state.rs +++ b/packages/clone-testing/src/state.rs @@ -5,7 +5,7 @@ use cw_orch_core::{ }; use cw_orch_daemon::DaemonState; use itertools::Itertools; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; #[derive(Clone, Debug)] /// Mock state for testing, stores addresses and code-ids. @@ -26,7 +26,7 @@ impl MockState { code_ids: HashMap::new(), daemon_state: DaemonState::new( DaemonState::state_file_path().unwrap(), - chain, + &Arc::new(chain), deployment_id.to_string(), true, false, diff --git a/packages/cw-orch-core/src/environment/cosmwasm_environment.rs b/packages/cw-orch-core/src/environment/cosmwasm_environment.rs index e2e6b5080..355ae198e 100644 --- a/packages/cw-orch-core/src/environment/cosmwasm_environment.rs +++ b/packages/cw-orch-core/src/environment/cosmwasm_environment.rs @@ -26,7 +26,20 @@ pub trait TxHandler: ChainState + Clone { type Sender: Clone; /// Gets the address of the current wallet used to sign transactions. - fn sender(&self) -> Addr; + #[deprecated( + since = "1.1.2", + note = "Please use `sender_addr` instead. This method will be changed in the next release." + )] + // TODO: return &Self::Sender here in the next breaking release + fn sender(&self) -> Addr { + self.sender_addr() + } + + /// Gets the address of the current wallet used to sign transactions. + fn sender_addr(&self) -> Addr { + // TODO: remove this default implementation in the next breaking release + unimplemented!("implement `sender_addr` for your chain handler"); + } /// Sets wallet to sign transactions. fn set_sender(&mut self, sender: Self::Sender); @@ -136,7 +149,7 @@ mod tests { type Sender = (); - fn sender(&self) -> Addr { + fn sender_addr(&self) -> Addr { unimplemented!() } diff --git a/packages/cw-orch-mock/src/core.rs b/packages/cw-orch-mock/src/core.rs index 21661982a..55e209c43 100644 --- a/packages/cw-orch-mock/src/core.rs +++ b/packages/cw-orch-mock/src/core.rs @@ -134,7 +134,7 @@ impl TxHandler for MockBase { type ContractSource = Box>; type Sender = Addr; - fn sender(&self) -> Addr { + fn sender_addr(&self) -> Addr { self.sender.clone() } @@ -308,7 +308,7 @@ mod test { asserting("sender is correct") .that(&sender.to_string()) - .is_equal_to(chain.sender().to_string()); + .is_equal_to(chain.sender_addr().to_string()); let contract_source = Box::new( ContractWrapper::new(execute, cw20_base::contract::instantiate, query) diff --git a/packages/cw-orch-mock/src/queriers/wasm.rs b/packages/cw-orch-mock/src/queriers/wasm.rs index 58e2a97c1..c7b497a4a 100644 --- a/packages/cw-orch-mock/src/queriers/wasm.rs +++ b/packages/cw-orch-mock/src/queriers/wasm.rs @@ -227,8 +227,11 @@ mod tests { )), )?; - mock.wasm_querier() - .instantiate2_addr(1, mock.sender(), Binary(b"salt-test".to_vec()))?; + mock.wasm_querier().instantiate2_addr( + 1, + mock.sender_addr(), + Binary(b"salt-test".to_vec()), + )?; Ok(()) } @@ -250,8 +253,11 @@ mod tests { )), )?; - mock.wasm_querier() - .instantiate2_addr(1, mock.sender(), Binary(b"salt-test".to_vec()))?; + mock.wasm_querier().instantiate2_addr( + 1, + mock.sender_addr(), + Binary(b"salt-test".to_vec()), + )?; Ok(()) } @@ -262,7 +268,7 @@ mod tests { let addr = mock.wasm_querier().instantiate2_addr( 0, - mock.sender(), + mock.sender_addr(), Binary(b"salt-test".to_vec()), )?; diff --git a/packages/cw-orch-mock/tests/conditional.rs b/packages/cw-orch-mock/tests/conditional.rs index d4f066916..06276248f 100644 --- a/packages/cw-orch-mock/tests/conditional.rs +++ b/packages/cw-orch-mock/tests/conditional.rs @@ -97,7 +97,7 @@ mod tests { #[test] fn cw_orch_interface_traits() { let chain = Mock::new("sender"); - let sender = chain.sender(); + let sender = chain.sender_addr(); let contract = mock_contract::MockContract::new("test:mock_contract", chain.clone()); diff --git a/packages/cw-orch-mock/tests/instantiate2.rs b/packages/cw-orch-mock/tests/instantiate2.rs index 056b77359..763239c8f 100644 --- a/packages/cw-orch-mock/tests/instantiate2.rs +++ b/packages/cw-orch-mock/tests/instantiate2.rs @@ -22,7 +22,7 @@ fn instantiate2() -> anyhow::Result<()> { let expected_address = app.wasm_querier().instantiate2_addr( mock_contract.code_id()?, - app.sender(), + app.sender_addr(), salt.clone(), )?; @@ -37,7 +37,7 @@ fn instantiate2() -> anyhow::Result<()> { .app .borrow() .api() - .addr_canonicalize(app.sender().as_str())?; + .addr_canonicalize(app.sender_addr().as_str())?; // If there is a `Invalid input: canonical address length not correct` error, that means this env doesn't work with instantiate2 correctly assert_eq!( diff --git a/packages/cw-orch-osmosis-test-tube/src/core.rs b/packages/cw-orch-osmosis-test-tube/src/core.rs index ad9cb0680..f8f907ad6 100644 --- a/packages/cw-orch-osmosis-test-tube/src/core.rs +++ b/packages/cw-orch-osmosis-test-tube/src/core.rs @@ -211,7 +211,7 @@ impl TxHandler for OsmosisTestTube { type Response = AppResponse; type Sender = Rc; - fn sender(&self) -> Addr { + fn sender_addr(&self) -> Addr { Addr::unchecked(self.sender.address()) } diff --git a/packages/interchain/interchain-daemon/src/interchain_env.rs b/packages/interchain/interchain-daemon/src/interchain_env.rs index b0ec67507..ebea2183e 100644 --- a/packages/interchain/interchain-daemon/src/interchain_env.rs +++ b/packages/interchain/interchain-daemon/src/interchain_env.rs @@ -87,10 +87,10 @@ impl DaemonInterchainEnv { &mut self, runtime: &Handle, chain_data: ChainInfoOwned, - mnemonic: Option, + mnemonic: Option>, ) -> IcDaemonResult<()> { - let mut daemon_builder = Daemon::builder(); - let mut daemon_builder = daemon_builder.chain(chain_data.clone()).handle(runtime); + let mut daemon_builder = Daemon::builder(chain_data); + let mut daemon_builder = daemon_builder.handle(runtime); daemon_builder = if let Some(mn) = mnemonic { daemon_builder.mnemonic(mn) diff --git a/packages/interchain/proto/src/ics20.rs b/packages/interchain/proto/src/ics20.rs index c1265f5fd..a159f7a8b 100644 --- a/packages/interchain/proto/src/ics20.rs +++ b/packages/interchain/proto/src/ics20.rs @@ -129,7 +129,6 @@ mod test { use anyhow::Result as AnyResult; use cosmwasm_std::coin; - use cw_orch_core::environment::TxHandler; use crate::tokenfactory::{ create_denom, create_transfer_channel, get_denom, mint, transfer_tokens, @@ -165,7 +164,7 @@ mod test { )> { let chain1 = interchain.chain(chain_id1).unwrap(); - let sender = chain1.sender().to_string(); + let sender = chain1.sender_addr().to_string(); let token_subdenom = format!( "{}{}", @@ -213,6 +212,8 @@ mod test { #[ignore] #[test] pub fn transfer_ics20_test() -> AnyResult<()> { + use cw_orch_core::environment::TxHandler; + logger_test_init(); let rt = Runtime::new().unwrap(); @@ -227,7 +228,7 @@ mod test { // This should pass ok, the timeout was set right let success_outcome = transfer_tokens( chain1, - chain2.sender().as_str(), + chain2.sender_addr().as_str(), &coin(TEST_AMOUNT / 2, denom.clone()), &interchain, &interchain_channel, @@ -244,7 +245,7 @@ mod test { // This should timeout let timeout_outcome = transfer_tokens( chain1, - chain2.sender().as_str(), + chain2.sender_addr().as_str(), &coin(TEST_AMOUNT / 2, denom), &interchain, &interchain_channel, diff --git a/packages/interchain/proto/src/tokenfactory.rs b/packages/interchain/proto/src/tokenfactory.rs index 943568fa6..773997f15 100644 --- a/packages/interchain/proto/src/tokenfactory.rs +++ b/packages/interchain/proto/src/tokenfactory.rs @@ -29,7 +29,7 @@ pub fn create_denom( chain: &Chain, token_name: &str, ) -> Result<(), ::Error> { - let creator = chain.sender().to_string(); + let creator = chain.sender_addr().to_string(); let any = MsgCreateDenom { sender: creator, @@ -54,7 +54,7 @@ pub fn create_denom( /// This actually creates the denom for a token created by an address (which is here taken to be the daemon sender address) /// This is mainly used for tests, but feel free to use that in production as well pub fn get_denom(daemon: &Chain, token_name: &str) -> String { - let sender = daemon.sender().to_string(); + let sender = daemon.sender_addr().to_string(); format!("factory/{}/{}", sender, token_name) } @@ -67,7 +67,7 @@ pub fn mint( token_name: &str, amount: u128, ) -> Result<(), ::Error> { - let sender = chain.sender().to_string(); + let sender = chain.sender_addr().to_string(); let denom = get_denom(chain, token_name); let any = MsgMint { @@ -119,7 +119,7 @@ pub fn transfer_tokens