1mod balance;
18mod call;
19mod contract_artifacts;
20mod contract_info;
21mod contract_storage;
22mod env_check;
23mod error;
24pub mod events;
25pub mod extrinsic_calls;
26pub mod extrinsic_opts;
27mod instantiate;
28pub mod pallet_contracts_primitives;
29mod remove;
30mod rpc;
31pub mod upload;
32
33#[cfg(test)]
34mod contract_storage_tests;
35
36#[cfg(test)]
37#[cfg(feature = "integration-tests")]
38mod integration_tests;
39
40use env_check::compare_node_env_with_contract;
41
42use anyhow::Result;
43use contract_build::{
44 CrateMetadata,
45 Verbosity,
46 DEFAULT_KEY_COL_WIDTH,
47};
48use scale::{
49 Decode,
50 Encode,
51};
52use subxt::{
53 backend::legacy::LegacyRpcMethods,
54 blocks,
55 config::{
56 DefaultExtrinsicParams,
57 DefaultExtrinsicParamsBuilder,
58 ExtrinsicParams,
59 },
60 tx,
61 Config,
62 OnlineClient,
63};
64
65pub use balance::{
66 BalanceVariant,
67 TokenMetadata,
68};
69pub use call::{
70 CallCommandBuilder,
71 CallExec,
72};
73pub use contract_artifacts::ContractArtifacts;
74pub use contract_info::{
75 fetch_all_contracts,
76 fetch_contract_info,
77 fetch_wasm_code,
78 ContractInfo,
79 TrieId,
80};
81use contract_metadata::ContractMetadata;
82pub use contract_storage::{
83 ContractStorage,
84 ContractStorageCell,
85 ContractStorageLayout,
86 ContractStorageRpc,
87};
88pub use contract_transcode::ContractMessageTranscoder;
89pub use error::{
90 ErrorVariant,
91 GenericError,
92};
93pub use events::DisplayEvents;
94pub use extrinsic_opts::ExtrinsicOptsBuilder;
95pub use instantiate::{
96 Code,
97 InstantiateArgs,
98 InstantiateCommandBuilder,
99 InstantiateDryRunResult,
100 InstantiateExec,
101 InstantiateExecResult,
102};
103pub use remove::{
104 RemoveCommandBuilder,
105 RemoveExec,
106 RemoveResult,
107};
108
109pub use upload::{
110 UploadCommandBuilder,
111 UploadExec,
112 UploadResult,
113};
114
115pub use rpc::{
116 RawParams,
117 RpcRequest,
118};
119
120#[derive(Debug, Clone)]
122pub struct WasmCode(Vec<u8>);
123
124impl WasmCode {
125 pub fn code_hash(&self) -> [u8; 32] {
127 contract_build::code_hash(&self.0)
128 }
129}
130
131async fn submit_extrinsic<C, Call, Signer>(
144 client: &OnlineClient<C>,
145 rpc: &LegacyRpcMethods<C>,
146 call: &Call,
147 signer: &Signer,
148) -> core::result::Result<blocks::ExtrinsicEvents<C>, subxt::Error>
149where
150 C: Config,
151 Call: tx::Payload,
152 Signer: tx::Signer<C>,
153 <C::ExtrinsicParams as ExtrinsicParams<C>>::Params:
154 From<<DefaultExtrinsicParams<C> as ExtrinsicParams<C>>::Params>,
155{
156 let account_id = Signer::account_id(signer);
157 let account_nonce = get_account_nonce(client, rpc, &account_id).await?;
158
159 let params = DefaultExtrinsicParamsBuilder::new()
160 .nonce(account_nonce)
161 .build();
162 let mut tx = client
163 .tx()
164 .create_signed_offline(call, signer, params.into())?
165 .submit_and_watch()
166 .await?;
167
168 use subxt::error::{
175 RpcError,
176 TransactionError,
177 };
178 use tx::TxStatus;
179
180 while let Some(status) = tx.next().await {
181 match status? {
182 TxStatus::InBestBlock(tx_in_block)
183 | TxStatus::InFinalizedBlock(tx_in_block) => {
184 let events = tx_in_block.wait_for_success().await?;
185 return Ok(events)
186 }
187 TxStatus::Error { message } => {
188 return Err(TransactionError::Error(message).into())
189 }
190 TxStatus::Invalid { message } => {
191 return Err(TransactionError::Invalid(message).into())
192 }
193 TxStatus::Dropped { message } => {
194 return Err(TransactionError::Dropped(message).into())
195 }
196 _ => continue,
197 }
198 }
199 Err(RpcError::SubscriptionDropped.into())
200}
201
202async fn get_account_nonce<C>(
204 client: &OnlineClient<C>,
205 rpc: &LegacyRpcMethods<C>,
206 account_id: &C::AccountId,
207) -> core::result::Result<u64, subxt::Error>
208where
209 C: Config,
210{
211 let best_block = rpc
212 .chain_get_block_hash(None)
213 .await?
214 .ok_or(subxt::Error::Other("Best block not found".into()))?;
215 let account_nonce = client
216 .blocks()
217 .at(best_block)
218 .await?
219 .account_nonce(account_id)
220 .await?;
221 Ok(account_nonce)
222}
223
224async fn state_call<C, A: Encode, R: Decode>(
225 rpc: &LegacyRpcMethods<C>,
226 func: &str,
227 args: A,
228) -> Result<R>
229where
230 C: Config,
231{
232 let params = args.encode();
233 let bytes = rpc.state_call(func, Some(¶ms), None).await?;
234 Ok(R::decode(&mut bytes.as_ref())?)
235}
236
237async fn get_best_block<C>(
239 rpc: &LegacyRpcMethods<C>,
240) -> core::result::Result<C::Hash, subxt::Error>
241where
242 C: Config,
243{
244 rpc.chain_get_block_hash(None)
245 .await?
246 .ok_or(subxt::Error::Other("Best block not found".into()))
247}
248
249fn check_env_types<C>(
250 client: &OnlineClient<C>,
251 transcoder: &ContractMessageTranscoder,
252 verbosity: &Verbosity,
253) -> Result<()>
254where
255 C: Config,
256{
257 compare_node_env_with_contract(
258 client.metadata().types(),
259 transcoder.metadata(),
260 verbosity,
261 )
262}
263
264pub fn url_to_string(url: &url::Url) -> String {
266 match (url.port(), url.port_or_known_default()) {
267 (None, Some(port)) => {
268 format!(
269 "{}:{port}{}",
270 &url[..url::Position::AfterHost],
271 &url[url::Position::BeforePath..]
272 )
273 .to_string()
274 }
275 _ => url.to_string(),
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn url_to_string_works() {
285 let url = url::Url::parse("ws://127.0.0.1:9944").unwrap();
287 assert_eq!(url_to_string(&url), "ws://127.0.0.1:9944/");
288
289 let url = url::Url::parse("wss://127.0.0.1:443").unwrap();
291 assert_eq!(url_to_string(&url), "wss://127.0.0.1:443/");
292
293 let url = url::Url::parse("wss://127.0.0.1:443/test/1").unwrap();
295 assert_eq!(url_to_string(&url), "wss://127.0.0.1:443/test/1");
296
297 let url = url::Url::parse("wss://test.io:443").unwrap();
299 assert_eq!(url_to_string(&url), "wss://test.io:443/");
300
301 let url = url::Url::parse("wss://test.io/test/1").unwrap();
303 assert_eq!(url_to_string(&url), "wss://test.io:443/test/1");
304 }
305}