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)]
39pub struct DaemonAsyncBase<Sender = Wallet> {
69 sender: Sender,
71 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 pub fn builder(chain: impl Into<ChainInfoOwned>) -> DaemonAsyncBuilder {
88 DaemonAsyncBuilder::new(chain)
89 }
90
91 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 pub fn sender_mut(&mut self) -> &mut Sender {
109 &mut self.sender
110 }
111
112 pub fn sender(&self) -> &Sender {
114 &self.sender
115 }
116
117 pub fn flush_state(&mut self) -> Result<(), DaemonError> {
120 self.state.flush()
121 }
122
123 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 is_test: false,
135 load_network: false,
137 }
138 }
139}
140
141impl<Sender: QuerySender> DaemonAsyncBase<Sender> {
142 pub fn channel(&self) -> Channel {
144 self.sender().channel()
145 }
146
147 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 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 tokio::time::sleep(wait_time).await;
177 while last_height < end_height {
179 tokio::time::sleep(average_block_speed).await;
182
183 last_height = Node::new_async(self.channel())._block_height().await?;
185 }
186 Ok(())
187 }
188
189 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 pub async fn next_block(&self) -> Result<(), DaemonError> {
198 self.wait_blocks(1).await
199 }
200
201 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
222impl<Sender: TxSender> DaemonAsyncBase<Sender> {
224 pub fn sender_addr(&self) -> Addr {
226 self.sender().address()
227 }
228
229 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 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 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 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 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 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 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 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}