use crate::{queriers::CosmWasm, DaemonState};
use super::{
builder::DaemonAsyncBuilder, cosmos_modules, error::DaemonError, queriers::Node,
sender::Wallet, tx_resp::CosmTxResponse,
};
use cosmrs::{
cosmwasm::{MsgExecuteContract, MsgInstantiateContract, MsgMigrateContract},
proto::cosmwasm::wasm::v1::MsgInstantiateContract2,
tendermint::Time,
AccountId, Any, Denom,
};
use cosmwasm_std::{Addr, Binary, Coin};
use cw_orch_core::{
contract::interface_traits::Uploadable,
environment::{ChainState, IndexResponse},
log::transaction_target,
};
use flate2::{write, Compression};
use prost::Message;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::from_str;
use std::{
fmt::Debug,
io::Write,
str::{from_utf8, FromStr},
sync::Arc,
time::Duration,
};
use tonic::transport::Channel;
#[derive(Clone)]
pub struct DaemonAsync {
pub sender: Wallet,
pub state: Arc<DaemonState>,
}
impl DaemonAsync {
pub fn builder() -> DaemonAsyncBuilder {
DaemonAsyncBuilder::default()
}
pub fn channel(&self) -> Channel {
self.state.grpc_channel.clone()
}
}
impl ChainState for DaemonAsync {
type Out = Arc<DaemonState>;
fn state(&self) -> Self::Out {
self.state.clone()
}
}
impl DaemonAsync {
pub fn sender(&self) -> Addr {
self.sender.address().unwrap()
}
pub fn rebuild(&self) -> DaemonAsyncBuilder {
let mut builder = Self::builder();
builder
.chain(self.state().chain_data.clone())
.sender((*self.sender).clone())
.deployment_id(&self.state().deployment_id);
builder
}
pub async fn execute<E: Serialize>(
&self,
exec_msg: &E,
coins: &[cosmwasm_std::Coin],
contract_address: &Addr,
) -> Result<CosmTxResponse, DaemonError> {
let exec_msg: MsgExecuteContract = MsgExecuteContract {
sender: self.sender.msg_sender()?,
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?;
log::info!(target: &transaction_target(), "Execution done: {:?}", result.txhash);
Ok(result)
}
pub async fn instantiate<I: Serialize + Debug>(
&self,
code_id: u64,
init_msg: &I,
label: Option<&str>,
admin: Option<&Addr>,
coins: &[Coin],
) -> Result<CosmTxResponse, DaemonError> {
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()?,
msg: serde_json::to_vec(&init_msg)?,
funds: parse_cw_coins(coins)?,
};
let result = sender.commit_tx(vec![init_msg], None).await?;
log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash);
Ok(result)
}
pub async fn instantiate2<I: Serialize + Debug>(
&self,
code_id: u64,
init_msg: &I,
label: Option<&str>,
admin: Option<&Addr>,
coins: &[Coin],
salt: Binary,
) -> Result<CosmTxResponse, DaemonError> {
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(),
msg: serde_json::to_vec(&init_msg)?,
funds: proto_parse_cw_coins(coins)?,
salt: salt.to_vec(),
fix_msg: false,
};
let result = sender
.commit_tx_any(
vec![Any {
type_url: "/cosmwasm.wasm.v1.MsgInstantiateContract2".to_string(),
value: init_msg.encode_to_vec(),
}],
None,
)
.await?;
log::info!(target: &transaction_target(), "Instantiation done: {:?}", result.txhash);
Ok(result)
}
pub async fn query<Q: Serialize + Debug, T: Serialize + DeserializeOwned>(
&self,
query_msg: &Q,
contract_address: &Addr,
) -> Result<T, DaemonError> {
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())?)
}
pub async fn migrate<M: Serialize + Debug>(
&self,
migrate_msg: &M,
new_code_id: u64,
contract_address: &Addr,
) -> Result<CosmTxResponse, DaemonError> {
let exec_msg: MsgMigrateContract = MsgMigrateContract {
sender: self.sender.msg_sender()?,
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?;
Ok(result)
}
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 * amount;
tokio::time::sleep(Duration::from_secs(wait_time)).await;
while last_height < end_height {
tokio::time::sleep(Duration::from_secs(average_block_speed)).await;
last_height = Node::new_async(self.channel())._block_height().await?;
}
Ok(())
}
pub async fn wait_seconds(&self, secs: u64) -> Result<(), DaemonError> {
tokio::time::sleep(Duration::from_secs(secs)).await;
Ok(())
}
pub async fn next_block(&self) -> Result<(), DaemonError> {
self.wait_blocks(1).await
}
pub async fn block_info(&self) -> Result<cosmwasm_std::BlockInfo, DaemonError> {
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(),
})
}
pub async fn upload<T: Uploadable>(
&self,
_uploadable: &T,
) -> Result<CosmTxResponse, DaemonError> {
let sender = &self.sender;
let wasm_path = <T as Uploadable>::wasm(&self.state.chain_data);
log::debug!(target: &transaction_target(), "Uploading file at {:?}", wasm_path);
let file_contents = std::fs::read(wasm_path.path())?;
let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&file_contents)?;
let wasm_byte_code = e.finish()?;
let store_msg = cosmrs::cosmwasm::MsgStoreCode {
sender: self.sender.msg_sender()?,
wasm_byte_code,
instantiate_permission: None,
};
let result = sender.commit_tx(vec![store_msg], None).await?;
log::info!(target: &transaction_target(), "Uploading done: {:?}", result.txhash);
let code_id = result.uploaded_code_id().unwrap();
let wasm = CosmWasm::new_async(self.channel());
while wasm._code(code_id).await.is_err() {
self.next_block().await?;
}
Ok(result)
}
pub fn set_sender(&mut self, sender: &Wallet) {
self.sender = sender.clone();
}
}
pub(crate) fn parse_cw_coins(
coins: &[cosmwasm_std::Coin],
) -> Result<Vec<cosmrs::Coin>, DaemonError> {
coins
.iter()
.map(|cosmwasm_std::Coin { amount, denom }| {
Ok(cosmrs::Coin {
amount: amount.u128(),
denom: Denom::from_str(denom)?,
})
})
.collect::<Result<Vec<_>, DaemonError>>()
}
pub(crate) fn proto_parse_cw_coins(
coins: &[cosmwasm_std::Coin],
) -> Result<Vec<cosmrs::proto::cosmos::base::v1beta1::Coin>, DaemonError> {
coins
.iter()
.map(|cosmwasm_std::Coin { amount, denom }| {
Ok(cosmrs::proto::cosmos::base::v1beta1::Coin {
amount: amount.to_string(),
denom: denom.clone(),
})
})
.collect::<Result<Vec<_>, DaemonError>>()
}