1use std::{
2 env,
3 str::{from_utf8, FromStr},
4 time::Duration,
5};
6
7use cosmrs::{
8 cosmwasm::{MsgExecuteContract, MsgInstantiateContract},
9 AccountId, Coin,
10};
11
12use serde::{de::DeserializeOwned, Serialize};
13use serde_json::from_str;
14
15use crate::{
16 cosmos_modules, data_structures::network::NetworkKind, error::CosmScriptError,
17 multisig::Multisig, sender::Wallet, CosmTxResponse, Deployment,
18};
19
20pub struct ContractInstance<'a> {
21 pub deployment: &'a Deployment,
22 pub name: &'a str,
23 pub sender: Wallet<'a>,
24 code_id_key: Option<&'a str>,
26}
27
28impl<'a> ContractInstance<'a> {
29 pub fn new(
30 name: &'a str,
31 sender: Wallet<'a>,
32 deployment: &'a Deployment,
33 ) -> anyhow::Result<Self> {
34 let instance = ContractInstance {
35 deployment,
36 name,
37 sender,
38 code_id_key: None,
39 };
40 Ok(instance)
41 }
42
43 pub fn overwrite_code_id_key(&mut self, code_id_key_to_use: &'static str) {
46 self.code_id_key = Some(code_id_key_to_use);
47 }
48
49 pub async fn execute<E: Serialize>(
50 &self,
51 exec_msg: &E,
52 coins: &[Coin],
53 ) -> Result<CosmTxResponse, CosmScriptError> {
54 let contract = self.get_address()?;
55 log::info!("executing on {} at {}", self.name, contract);
56
57 let exec_msg: MsgExecuteContract = if self.deployment.proposal {
58 Multisig::create_proposal(
59 &exec_msg,
60 &self.deployment.name,
61 &contract,
62 &env::var(&self.deployment.network.kind.multisig_name())?,
63 self.sender.pub_addr()?,
64 coins,
65 )?
66 } else {
67 MsgExecuteContract {
68 sender: self.sender.pub_addr()?,
69 contract: AccountId::from_str(&self.get_address()?)?,
70 msg: serde_json::to_vec(&exec_msg)?,
71 funds: coins.to_vec(),
72 }
73 };
74
75 let result = self.sender.commit_tx(vec![exec_msg], None).await?;
76
77 Ok(result)
78 }
79
80 pub async fn instantiate<I: Serialize>(
81 &self,
82 init_msg: I,
83 admin: Option<String>,
84 coins: &[Coin],
85 ) -> Result<CosmTxResponse, CosmScriptError> {
86 let sender = self.sender;
87 let key = self.code_id_key.unwrap_or(self.name);
88 let code_id = self.deployment.network.get_latest_version(key)?;
89
90 let memo = format!("Contract: {}, Group: {}", self.name, self.deployment.name);
91
92 log::info!("instantiating {}", self.name);
93
94 let init_msg = MsgInstantiateContract {
95 code_id,
96 label: Some(self.name.into()),
97 admin: admin.map(|a| FromStr::from_str(&a).unwrap()),
98 sender: sender.pub_addr()?,
99 msg: serde_json::to_vec(&init_msg)?,
100 funds: coins.to_vec(),
101 };
102
103 let result = sender.commit_tx(vec![init_msg], Some(&memo)).await?;
104 let address = &result.get_attribute_from_logs("instantiate", "_contract_address")[0].1;
105
106 log::debug!("{} address: {:?}", self.name, address);
107 self.save_contract_address(address)?;
108
109 Ok(result)
110 }
111
112 pub async fn query<Q: Serialize, T: Serialize + DeserializeOwned>(
113 &self,
114 query_msg: Q,
115 ) -> Result<T, CosmScriptError> {
116 let sender = self.sender;
117
118 let mut client = cosmos_modules::cosmwasm::query_client::QueryClient::new(sender.channel());
119 let resp = client
120 .smart_contract_state(cosmos_modules::cosmwasm::QuerySmartContractStateRequest {
121 address: self.get_address()?,
122 query_data: serde_json::to_vec(&query_msg)?,
123 })
124 .await?;
125
126 Ok(from_str(from_utf8(&resp.into_inner().data).unwrap())?)
127 }
128
129 pub async fn upload(&self, path: &str) -> Result<CosmTxResponse, CosmScriptError> {
133 let sender = &self.sender;
134 let memo = format!("Contract: {}, Group: {}", self.name, self.deployment.name);
135 let wasm_path = if path.contains(".wasm") {
136 path.to_string()
137 } else {
138 format!("{}/{}.wasm", env::var("WASM_DIR").unwrap(), path)
139 };
140
141 log::debug!("{}", wasm_path);
142
143 let file_contents = std::fs::read(wasm_path)?;
144 let store_msg = cosmrs::cosmwasm::MsgStoreCode {
145 sender: sender.pub_addr()?,
146 wasm_byte_code: file_contents,
147 instantiate_permission: None,
148 };
149 let result = sender.commit_tx(vec![store_msg], Some(&memo)).await?;
150
151 log::info!("uploaded: {:?}", result.txhash);
152 let code_id = result.get_attribute_from_logs("store_code", "code_id")[0]
155 .1
156 .parse::<u64>()?;
157 log::info!("code_id: {:?}", code_id);
158 self.save_code_id(code_id)?;
159 wait(self.deployment).await;
160 Ok(result)
161 }
162
163 pub async fn migrate<M: Serialize>(
164 &self,
165 _migrate_msg: M,
166 _new_code_id: u64,
167 ) -> Result<CosmTxResponse, CosmScriptError> {
168 todo!()
169
170 }
198
199 pub fn get_address(&self) -> Result<String, CosmScriptError> {
202 self.deployment.get_contract_address(self.name)
203 }
204
205 pub async fn get_code_id(&self) -> Result<u64, CosmScriptError> {
207 let addr = self.get_address()?;
208 let channel = self.deployment.network.grpc_channel.clone();
209 let mut client = cosmos_modules::cosmwasm::query_client::QueryClient::new(channel);
210
211 let resp = client
212 .contract_info(cosmos_modules::cosmwasm::QueryContractInfoRequest { address: addr })
213 .await?
214 .into_inner();
215
216 let code_id = resp.contract_info.unwrap().code_id;
217 Ok(code_id)
218 }
219
220 pub fn save_code_id(&self, code_id: u64) -> Result<(), CosmScriptError> {
223 self.deployment
224 .network
225 .set_contract_version(self.name, code_id)
226 }
227
228 pub fn save_contract_address(&self, contract_address: &str) -> Result<(), CosmScriptError> {
229 self.deployment
230 .save_contract_address(self.name, contract_address)
231 }
232
233 pub async fn is_local_version(&self) -> anyhow::Result<bool> {
234 todo!()
235
236 }
268}
269
270async fn wait(deployment: &Deployment) {
271 match deployment.network.kind {
272 NetworkKind::Local => tokio::time::sleep(Duration::from_secs(6)).await,
273 NetworkKind::Mainnet => tokio::time::sleep(Duration::from_secs(60)).await,
274 NetworkKind::Testnet => tokio::time::sleep(Duration::from_secs(30)).await,
275 }
276}