Skip to main content

cw_orch_daemon/
core.rs

1use super::{
2    cosmos_modules, error::DaemonError, queriers::Node, senders::Wallet, tx_resp::CosmTxResponse,
3};
4use crate::{
5    queriers::CosmWasm,
6    senders::{builder::SenderBuilder, query::QuerySender, tx::TxSender},
7    DaemonAsyncBuilder, DaemonState,
8};
9use cosmrs::{
10    cosmwasm::{MsgExecuteContract, MsgInstantiateContract, MsgMigrateContract},
11    proto::cosmwasm::wasm::v1::MsgInstantiateContract2,
12    tendermint::Time,
13    AccountId, Any, Denom,
14};
15use cosmwasm_std::{Addr, Binary, Coin};
16use cw_orch_core::{
17    contract::{interface_traits::Uploadable, WasmPath},
18    environment::{
19        AccessConfig, AsyncWasmQuerier, ChainInfoOwned, ChainState, IndexResponse, Querier,
20    },
21    log::transaction_target,
22};
23use flate2::{write, Compression};
24use prost::Message;
25use serde::{de::DeserializeOwned, Serialize};
26use serde_json::from_str;
27use std::{
28    fmt::Debug,
29    io::Write,
30    ops::Deref,
31    str::{from_utf8, FromStr},
32    time::Duration,
33};
34use tonic::transport::Channel;
35
36pub const INSTANTIATE_2_TYPE_URL: &str = "/cosmwasm.wasm.v1.MsgInstantiateContract2";
37
38#[derive(Clone)]
39/**
40    Represents a blockchain node.
41    It's constructed using [`DaemonAsyncBuilder`].
42
43    ## Usage
44    ```rust,no_run
45    # tokio_test::block_on(async {
46    use cw_orch_daemon::{DaemonAsync, networks};
47
48    let daemon: DaemonAsync = DaemonAsync::builder(networks::JUNO_1)
49        .build()
50        .await.unwrap();
51    # })
52    ```
53    ## Environment Execution
54
55    The DaemonAsync implements async methods of [`TxHandler`](cw_orch_core::environment::TxHandler) which allows you to perform transactions on the chain.
56
57    ## Querying
58
59    Different Cosmos SDK modules can be queried through the daemon by calling the [`DaemonAsync::query_client<Querier>`] method with a specific querier.
60    See [Querier](crate::queriers) for examples.
61
62    ## Warning
63
64    This daemon is thread safe and can be used between threads.
65    However, please make sure that you are not trying to broadcast multiple transactions at once when using this Daemon on different threads.
66    If you do so, you will get account sequence errors and your transactions won't get broadcasted.
67*/
68pub struct DaemonAsyncBase<Sender = Wallet> {
69    /// Sender to send transactions to the chain
70    sender: Sender,
71    /// State of the daemon
72    pub(crate) state: DaemonState,
73}
74
75pub type DaemonAsync = DaemonAsyncBase<Wallet>;
76
77impl<Sender> DaemonAsyncBase<Sender> {
78    pub(crate) fn new(sender: Sender, state: DaemonState) -> Self {
79        Self { sender, state }
80    }
81
82    pub fn chain_info(&self) -> &ChainInfoOwned {
83        self.state.chain_data.as_ref()
84    }
85
86    /// Get the daemon builder
87    pub fn builder(chain: impl Into<ChainInfoOwned>) -> DaemonAsyncBuilder {
88        DaemonAsyncBuilder::new(chain)
89    }
90
91    /// Set the Sender for use with this Daemon
92    /// The sender will be configured with the chain's data.
93    pub async fn new_sender<T: SenderBuilder>(
94        self,
95        sender_options: T,
96    ) -> DaemonAsyncBase<T::Sender> {
97        let sender = sender_options
98            .build(&self.state.chain_data)
99            .await
100            .expect("Failed to build sender");
101        DaemonAsyncBase {
102            sender,
103            state: self.state,
104        }
105    }
106
107    /// Get a mutable Sender
108    pub fn sender_mut(&mut self) -> &mut Sender {
109        &mut self.sender
110    }
111
112    // Get a read-only Sender
113    pub fn sender(&self) -> &Sender {
114        &self.sender
115    }
116
117    /// Flushes all the state related to the current chain
118    /// Only works on Local networks
119    pub fn flush_state(&mut self) -> Result<(), DaemonError> {
120        self.state.flush()
121    }
122
123    /// Returns a new [`DaemonAsyncBuilder`] with the current configuration.
124    /// Does not consume the original [`DaemonAsync`].
125    pub fn rebuild(&self) -> DaemonAsyncBuilder {
126        DaemonAsyncBuilder {
127            state: Some(self.state()),
128            chain: self.state.chain_data.deref().clone(),
129            deployment_id: Some(self.state.deployment_id.clone()),
130            state_path: None,
131            write_on_change: None,
132            mnemonic: None,
133            // If it was test it will just use same tempfile as state
134            is_test: false,
135            // Uses same ChainInfo
136            load_network: false,
137        }
138    }
139}
140
141impl<Sender: QuerySender> DaemonAsyncBase<Sender> {
142    /// Get the channel configured for this DaemonAsync.
143    pub fn channel(&self) -> Channel {
144        self.sender().channel()
145    }
146
147    /// Query a contract.
148    pub async fn query<Q: Serialize + Debug, T: Serialize + DeserializeOwned>(
149        &self,
150        query_msg: &Q,
151        contract_address: &Addr,
152    ) -> Result<T, DaemonError> {
153        let mut client = cosmos_modules::cosmwasm::query_client::QueryClient::new(self.channel());
154        let resp = client
155            .smart_contract_state(cosmos_modules::cosmwasm::QuerySmartContractStateRequest {
156                address: contract_address.to_string(),
157                query_data: serde_json::to_vec(&query_msg)?,
158            })
159            .await?;
160
161        Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?)
162    }
163
164    /// Wait for a given amount of blocks.
165    pub async fn wait_blocks(&self, amount: u64) -> Result<(), DaemonError> {
166        let mut last_height = Node::new_async(self.channel())._block_height().await?;
167        let end_height = last_height + amount;
168
169        let average_block_speed = Node::new_async(self.channel())
170            ._average_block_speed(Some(0.9))
171            .await?;
172
173        let wait_time = average_block_speed.mul_f64(amount as f64);
174
175        // now wait for that amount of time
176        tokio::time::sleep(wait_time).await;
177        // now check every block until we hit the target
178        while last_height < end_height {
179            // wait
180
181            tokio::time::sleep(average_block_speed).await;
182
183            // ping latest block
184            last_height = Node::new_async(self.channel())._block_height().await?;
185        }
186        Ok(())
187    }
188
189    /// Wait for a given amount of seconds.
190    pub async fn wait_seconds(&self, secs: u64) -> Result<(), DaemonError> {
191        tokio::time::sleep(Duration::from_secs(secs)).await;
192
193        Ok(())
194    }
195
196    /// Wait for the next block.
197    pub async fn next_block(&self) -> Result<(), DaemonError> {
198        self.wait_blocks(1).await
199    }
200
201    /// Get the current block info.
202    pub async fn block_info(&self) -> Result<cosmwasm_std::BlockInfo, DaemonError> {
203        let block = Node::new_async(self.channel())._latest_block().await?;
204        let since_epoch = block.header.time.duration_since(Time::unix_epoch())?;
205        let time = cosmwasm_std::Timestamp::from_nanos(since_epoch.as_nanos() as u64);
206        Ok(cosmwasm_std::BlockInfo {
207            height: block.header.height.value(),
208            time,
209            chain_id: block.header.chain_id.to_string(),
210        })
211    }
212}
213
214impl<Sender> ChainState for DaemonAsyncBase<Sender> {
215    type Out = DaemonState;
216
217    fn state(&self) -> Self::Out {
218        self.state.clone()
219    }
220}
221
222// Execute on the real chain, returns tx response.
223impl<Sender: TxSender> DaemonAsyncBase<Sender> {
224    /// Get the sender address
225    pub fn sender_addr(&self) -> Addr {
226        self.sender().address()
227    }
228
229    /// Execute a message on a contract.
230    pub async fn execute<E: Serialize>(
231        &self,
232        exec_msg: &E,
233        coins: &[cosmwasm_std::Coin],
234        contract_address: &Addr,
235    ) -> Result<CosmTxResponse, DaemonError> {
236        let exec_msg: MsgExecuteContract = MsgExecuteContract {
237            sender: self.sender().msg_sender().map_err(Into::into)?,
238            contract: AccountId::from_str(contract_address.as_str())?,
239            msg: serde_json::to_vec(&exec_msg)?,
240            funds: parse_cw_coins(coins)?,
241        };
242        let result = self
243            .sender()
244            .commit_tx(vec![exec_msg], None)
245            .await
246            .map_err(Into::into)?;
247        log::info!(target: &transaction_target(), "Execution done: {:?}", result.txhash);
248
249        Ok(result)
250    }
251
252    /// Instantiate a contract.
253    pub async fn instantiate<I: Serialize + Debug>(
254        &self,
255        code_id: u64,
256        init_msg: &I,
257        label: Option<&str>,
258        admin: Option<&Addr>,
259        coins: &[Coin],
260    ) -> Result<CosmTxResponse, DaemonError> {
261        let init_msg = MsgInstantiateContract {
262            code_id,
263            label: Some(label.unwrap_or("instantiate_contract").to_string()),
264            admin: admin.map(|a| FromStr::from_str(a.as_str()).unwrap()),
265            sender: self.sender().msg_sender().map_err(Into::into)?,
266            msg: serde_json::to_vec(&init_msg)?,
267            funds: parse_cw_coins(coins)?,
268        };
269
270        let result = self
271            .sender()
272            .commit_tx(vec![init_msg], None)
273            .await
274            .map_err(Into::into)?;
275
276        log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash);
277
278        Ok(result)
279    }
280
281    /// Instantiate a contract.
282    pub async fn instantiate2<I: Serialize + Debug>(
283        &self,
284        code_id: u64,
285        init_msg: &I,
286        label: Option<&str>,
287        admin: Option<&Addr>,
288        coins: &[Coin],
289        salt: Binary,
290    ) -> Result<CosmTxResponse, DaemonError> {
291        let init_msg = MsgInstantiateContract2 {
292            code_id,
293            label: label.unwrap_or("instantiate_contract").to_string(),
294            admin: admin.map(Into::into).unwrap_or_default(),
295            sender: self.sender_addr().to_string(),
296            msg: serde_json::to_vec(&init_msg)?,
297            funds: proto_parse_cw_coins(coins)?,
298            salt: salt.to_vec(),
299            fix_msg: false,
300        };
301
302        let result = self
303            .sender()
304            .commit_tx_any(
305                vec![Any {
306                    type_url: INSTANTIATE_2_TYPE_URL.to_string(),
307                    value: init_msg.encode_to_vec(),
308                }],
309                None,
310            )
311            .await
312            .map_err(Into::into)?;
313
314        log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash);
315
316        Ok(result)
317    }
318
319    /// Migration a contract.
320    pub async fn migrate<M: Serialize + Debug>(
321        &self,
322        migrate_msg: &M,
323        new_code_id: u64,
324        contract_address: &Addr,
325    ) -> Result<CosmTxResponse, DaemonError> {
326        let exec_msg: MsgMigrateContract = MsgMigrateContract {
327            sender: self.sender().msg_sender().map_err(Into::into)?,
328            contract: AccountId::from_str(contract_address.as_str())?,
329            msg: serde_json::to_vec(&migrate_msg)?,
330            code_id: new_code_id,
331        };
332        let result = self
333            .sender()
334            .commit_tx(vec![exec_msg], None)
335            .await
336            .map_err(Into::into)?;
337        Ok(result)
338    }
339
340    /// Upload a contract to the chain.
341    pub async fn upload<T: Uploadable>(
342        &self,
343        uploadable: &T,
344    ) -> Result<CosmTxResponse, DaemonError> {
345        self.upload_with_access_config(uploadable, None).await
346    }
347
348    /// Upload a contract to the chain and specify the permissions for instantiating
349    pub async fn upload_with_access_config<T: Uploadable>(
350        &self,
351        _uploadable: &T,
352        access: Option<AccessConfig>,
353    ) -> Result<CosmTxResponse, DaemonError> {
354        let wasm_path = <T as Uploadable>::wasm(self.chain_info());
355
356        log::debug!(target: &transaction_target(), "Uploading file at {:?}", wasm_path);
357
358        let result = upload_wasm(self.sender(), wasm_path, access).await?;
359
360        log::info!(target: &transaction_target(), "Uploading done: {:?}", result.txhash);
361
362        let code_id = result.uploaded_code_id().unwrap();
363
364        // wait for the node to return the contract information for this upload
365        let wasm = CosmWasm::new_async(self.channel());
366        while wasm._code(code_id).await.is_err() {
367            self.next_block().await?;
368        }
369        Ok(result)
370    }
371}
372
373pub async fn upload_wasm<T: TxSender>(
374    sender: &T,
375    wasm_path: WasmPath,
376    access: Option<AccessConfig>,
377) -> Result<CosmTxResponse, DaemonError> {
378    let file_contents = std::fs::read(wasm_path.path())?;
379    let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
380    e.write_all(&file_contents)?;
381    let wasm_byte_code = e.finish()?;
382    let store_msg = cosmrs::cosmwasm::MsgStoreCode {
383        sender: sender.msg_sender().map_err(Into::into)?,
384        wasm_byte_code,
385        instantiate_permission: access.map(access_config_to_cosmrs).transpose()?,
386    };
387
388    sender
389        .commit_tx(vec![store_msg], None)
390        .await
391        .map_err(Into::into)
392}
393
394pub(crate) fn access_config_to_cosmrs(
395    access_config: AccessConfig,
396) -> Result<cosmrs::cosmwasm::AccessConfig, DaemonError> {
397    let response = match access_config {
398        AccessConfig::Nobody => cosmrs::cosmwasm::AccessConfig {
399            permission: cosmrs::cosmwasm::AccessType::Nobody,
400            addresses: vec![],
401        },
402        AccessConfig::Everybody => cosmrs::cosmwasm::AccessConfig {
403            permission: cosmrs::cosmwasm::AccessType::Everybody,
404            addresses: vec![],
405        },
406        AccessConfig::AnyOfAddresses(addresses) => cosmrs::cosmwasm::AccessConfig {
407            permission: cosmrs::cosmwasm::AccessType::AnyOfAddresses,
408            addresses: addresses
409                .into_iter()
410                .map(|a| a.parse())
411                .collect::<Result<_, _>>()?,
412        },
413        AccessConfig::Unspecified => cosmrs::cosmwasm::AccessConfig {
414            permission: cosmrs::cosmwasm::AccessType::Unspecified,
415            addresses: vec![],
416        },
417    };
418    Ok(response)
419}
420
421impl Querier for DaemonAsync {
422    type Error = DaemonError;
423}
424
425impl AsyncWasmQuerier for DaemonAsync {
426    /// Query a contract.
427    fn smart_query<Q: Serialize + Sync, T: DeserializeOwned>(
428        &self,
429        address: &Addr,
430        query_msg: &Q,
431    ) -> impl std::future::Future<Output = Result<T, DaemonError>> + Send {
432        let query_data = serde_json::to_vec(query_msg).unwrap();
433        async {
434            let mut client =
435                cosmos_modules::cosmwasm::query_client::QueryClient::new(self.channel());
436            let resp = client
437                .smart_contract_state(cosmos_modules::cosmwasm::QuerySmartContractStateRequest {
438                    address: address.into(),
439                    query_data,
440                })
441                .await?;
442            Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?)
443        }
444    }
445}
446
447pub fn parse_cw_coins(coins: &[cosmwasm_std::Coin]) -> Result<Vec<cosmrs::Coin>, DaemonError> {
448    coins
449        .iter()
450        .map(|cosmwasm_std::Coin { amount, denom }| {
451            Ok(cosmrs::Coin {
452                amount: amount.u128(),
453                denom: Denom::from_str(denom)?,
454            })
455        })
456        .collect::<Result<Vec<_>, DaemonError>>()
457}
458
459pub(crate) fn proto_parse_cw_coins(
460    coins: &[cosmwasm_std::Coin],
461) -> Result<Vec<cosmrs::proto::cosmos::base::v1beta1::Coin>, DaemonError> {
462    coins
463        .iter()
464        .map(|cosmwasm_std::Coin { amount, denom }| {
465            Ok(cosmrs::proto::cosmos::base::v1beta1::Coin {
466                amount: amount.to_string(),
467                denom: denom.clone(),
468            })
469        })
470        .collect::<Result<Vec<_>, DaemonError>>()
471}