Skip to main content

casper_rust_wasm_sdk/sdk/contract/
query_contract_key.rs

1#[cfg(all(feature = "js", target_arch = "wasm32"))]
2use crate::types::identifier::block_identifier::BlockIdentifier;
3#[cfg(all(feature = "js", target_arch = "wasm32"))]
4use crate::{rpcs::query_global_state::QueryGlobalStateResult, types::path::Path};
5use crate::{
6    rpcs::query_global_state::{KeyIdentifierInput, PathIdentifierInput, QueryGlobalStateParams},
7    types::{
8        identifier::{block_identifier::BlockIdentifierInput, entity_identifier::EntityIdentifier},
9        sdk_error::SdkError,
10        verbosity::Verbosity,
11    },
12    SDK,
13};
14use casper_client::{
15    rpcs::results::QueryGlobalStateResult as _QueryGlobalStateResult, SuccessResponse,
16};
17#[cfg(all(feature = "js", target_arch = "wasm32"))]
18use gloo_utils::format::JsValueSerdeExt;
19#[cfg(all(feature = "js", target_arch = "wasm32"))]
20use serde::{Deserialize, Serialize};
21#[cfg(all(feature = "js", target_arch = "wasm32"))]
22use wasm_bindgen::prelude::*;
23
24#[derive(Deserialize, Default, Serialize)]
25#[cfg(all(feature = "js", target_arch = "wasm32"))]
26#[cfg_attr(
27    feature = "js",
28    wasm_bindgen(js_name = "queryContractKeyOptions", getter_with_clone)
29)]
30pub struct QueryContractKeyOptions {
31    pub entity_identifier: Option<EntityIdentifier>,
32    pub entity_identifier_as_string: Option<String>,
33    pub maybe_block_identifier: Option<BlockIdentifier>,
34    pub maybe_block_id_as_string: Option<String>,
35    pub path_as_string: Option<String>,
36    pub path: Option<Path>,
37    pub rpc_address: Option<String>,
38    pub verbosity: Option<Verbosity>,
39}
40
41#[cfg(all(feature = "js", target_arch = "wasm32"))]
42#[cfg_attr(feature = "js", wasm_bindgen)]
43impl SDK {
44    /// Deserialize query_contract_key_options from a JavaScript object.
45    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_contract_key_options"))]
46    #[cfg(feature = "js")]
47    pub fn query_contract_key_state_options(
48        &self,
49        options: JsValue,
50    ) -> Result<QueryContractKeyOptions, JsError> {
51        options
52            .into_serde::<QueryContractKeyOptions>()
53            .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
54    }
55
56    /// JavaScript function for query_contract_key with deserialized options.
57    #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_contract_key"))]
58    #[cfg(feature = "js")]
59    pub async fn query_contract_key_js_alias(
60        &self,
61        options: Option<QueryContractKeyOptions>,
62    ) -> Result<QueryGlobalStateResult, JsError> {
63        let options = options.unwrap_or_default();
64
65        // Ensure valid conversion of `path` from `QueryContractKeyOptions`
66        let path_input = match (options.path, options.path_as_string) {
67            (Some(path), None) => PathIdentifierInput::Path(path),
68            (None, Some(path_string)) => PathIdentifierInput::String(path_string),
69            (Some(_), Some(_)) => {
70                let err = "Only one of `path` or `path_as_string` can be provided".to_string();
71                return Err(JsError::new(&err));
72            }
73            (None, None) => {
74                let err = "Either `path` or `path_as_string` must be provided".to_string();
75                return Err(JsError::new(&err));
76            }
77        };
78
79        let maybe_block_identifier =
80            if let Some(maybe_block_identifier) = options.maybe_block_identifier {
81                Some(BlockIdentifierInput::BlockIdentifier(
82                    maybe_block_identifier,
83                ))
84            } else {
85                options
86                    .maybe_block_id_as_string
87                    .map(BlockIdentifierInput::String)
88            };
89
90        let result = self
91            .query_contract_key(
92                options.entity_identifier,
93                options.entity_identifier_as_string,
94                path_input,
95                maybe_block_identifier,
96                options.verbosity,
97                options.rpc_address,
98            )
99            .await;
100        match result {
101            Ok(data) => Ok(data.result.into()),
102            Err(err) => {
103                let err = &format!("Error occurred with {err:?}");
104                Err(JsError::new(err))
105            }
106        }
107    }
108}
109
110/// Alias of sdk.query_global_state
111impl SDK {
112    /// Query a contract key.
113    ///
114    /// # Arguments
115    ///
116    /// * `query_params` - Query global state parameters.
117    ///
118    /// # Returns
119    ///
120    /// A `Result` containing either a `SuccessResponse<_GetAddressableEntityResult>` or a `SdkError` in case of an error.
121    pub async fn query_contract_key(
122        &self,
123        entity_identifier: Option<EntityIdentifier>,
124        entity_identifier_as_string: Option<String>,
125        path: PathIdentifierInput,
126        maybe_block_identifier: Option<BlockIdentifierInput>,
127        verbosity: Option<Verbosity>,
128        rpc_address: Option<String>,
129    ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
130        match path {
131            PathIdentifierInput::Path(ref path_struct) => {
132                if path_struct.is_empty() {
133                    return Err(SdkError::InvalidArgument {
134                        context: "Path",
135                        error: "Path is empty".to_string(),
136                    });
137                }
138                path_struct.to_string()
139            }
140            PathIdentifierInput::String(ref path_string) => {
141                if path_string.is_empty() {
142                    return Err(SdkError::InvalidArgument {
143                        context: "Path string",
144                        error: "Path string is empty".to_string(),
145                    });
146                }
147                path_string.clone()
148            }
149        };
150
151        //log("query_contract_key!");
152        let entity = self
153            .get_entity(
154                entity_identifier.clone(),
155                entity_identifier_as_string.clone(),
156                maybe_block_identifier.clone(),
157                verbosity,
158                rpc_address.clone(),
159            )
160            .await;
161
162        let key_as_string = entity_identifier_as_string
163            .or_else(|| entity_identifier.as_ref().map(ToString::to_string))
164            .unwrap_or_default();
165
166        let maybe_block_id: Option<String> = match maybe_block_identifier.clone() {
167            Some(block_identifier) => match block_identifier {
168                BlockIdentifierInput::BlockIdentifier(_) => {
169                    maybe_block_identifier.map(|b| b.to_string())
170                }
171                BlockIdentifierInput::String(block_identifier_as_string) => {
172                    Some(block_identifier_as_string.clone())
173                }
174            },
175            None => None,
176        };
177
178        match entity {
179            // Entities enabled
180            Ok(_) => {
181                let key = KeyIdentifierInput::String(key_as_string);
182                self.query_global_state(QueryGlobalStateParams {
183                    key,
184                    path: Some(path),
185                    maybe_global_state_identifier: None,
186                    state_root_hash: None,
187                    maybe_block_id,
188                    verbosity,
189                    rpc_address,
190                })
191                .await
192            }
193            Err(_) => {
194                // Entities not enabled
195                let key_as_string =
196                    crate::helpers::contract_hash_key_for_global_state(&key_as_string);
197                let key = KeyIdentifierInput::String(key_as_string);
198                self.query_global_state(QueryGlobalStateParams {
199                    key,
200                    path: Some(path),
201                    maybe_global_state_identifier: None,
202                    state_root_hash: None,
203                    maybe_block_id,
204                    verbosity,
205                    rpc_address,
206                })
207                .await
208            }
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::{
217        install_cep78,
218        types::{
219            identifier::{block_identifier::BlockIdentifier, entity_identifier::EntityIdentifier},
220            verbosity::Verbosity,
221        },
222    };
223    use sdk_tests::tests::helpers::{
224        get_block, get_enable_addressable_entity, get_network_constants,
225    };
226    use tokio;
227
228    async fn get_entity_input() -> EntityIdentifier {
229        EntityIdentifier::from_formatted_str(&install_cep78().await).unwrap()
230    }
231
232    #[tokio::test]
233    async fn test_query_contract_key_with_none_values() {
234        // Arrange
235        let sdk = SDK::new(None, None, None);
236        let error_message = "Invalid argument 'Path string': Path string is empty";
237
238        let path = PathIdentifierInput::String("".to_string());
239
240        // Act
241        let result = sdk
242            .query_contract_key(Some(get_entity_input().await), None, path, None, None, None)
243            .await;
244
245        // Assert
246        assert!(result.is_err());
247        let err_string = result.err().unwrap().to_string();
248        assert!(err_string.contains(error_message));
249    }
250
251    #[tokio::test]
252    async fn test_query_contract_key_with_missing_key() {
253        // Arrange
254        let sdk = SDK::new(None, None, None);
255        let error_message = if get_enable_addressable_entity() {
256            "Invalid argument 'get_entity': Error: Missing entity identifier"
257        } else {
258            "Invalid argument 'query_global_state': Error: Missing key from formatted string"
259        };
260
261        let path = PathIdentifierInput::String("installer".to_string());
262
263        // Act
264        let result = sdk
265            .query_contract_key(None, None, path, None, None, None)
266            .await;
267
268        // Assert
269        assert!(result.is_err());
270        let err_string = result.err().unwrap().to_string();
271        assert!(err_string.contains(error_message));
272    }
273
274    #[tokio::test]
275    async fn test_query_contract_key_with_entity_identifier_as_string() {
276        // Arrange
277        let sdk = SDK::new(None, None, None);
278        let verbosity = Some(Verbosity::High);
279        let (rpc_address, _, _, _, _) = get_network_constants();
280
281        let entity = get_entity_input().await;
282
283        let path = PathIdentifierInput::String("installer".to_string());
284
285        let (_, block_height) = get_block(&rpc_address.clone()).await;
286        let block_identifier =
287            BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
288
289        // Act
290        let result = sdk
291            .query_contract_key(
292                Some(entity),
293                None,
294                path,
295                Some(block_identifier),
296                verbosity,
297                Some(rpc_address),
298            )
299            .await;
300
301        // Assert
302        assert!(result.is_ok());
303    }
304
305    #[tokio::test]
306    async fn test_query_contract_key_with_global_state_identifier() {
307        // Arrange
308        let sdk = SDK::new(None, None, None);
309        let verbosity = Some(Verbosity::High);
310        let (rpc_address, _, _, _, _) = get_network_constants();
311
312        let entity = get_entity_input().await;
313
314        let path = PathIdentifierInput::String("installer".to_string());
315
316        let (_, block_height) = get_block(&rpc_address.clone()).await;
317        let block_identifier =
318            BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
319        // Act
320        let result = sdk
321            .query_contract_key(
322                None,
323                Some(entity.to_string()),
324                path,
325                Some(block_identifier),
326                verbosity,
327                Some(rpc_address),
328            )
329            .await;
330
331        // Assert
332        assert!(result.is_ok());
333    }
334
335    #[tokio::test]
336    async fn test_query_contract_key_with_missing_path() {
337        // Arrange
338        let sdk = SDK::new(None, None, None);
339        let verbosity = Some(Verbosity::High);
340        let (rpc_address, _, _, _, _) = get_network_constants();
341
342        let entity = get_entity_input().await;
343        let error_message = "Invalid argument 'Path': Path is empty";
344
345        let vec_of_strings = vec!["".to_string()]; // empty path should error
346        let path = PathIdentifierInput::Path(vec_of_strings.into());
347
348        // Act
349        let result = sdk
350            .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
351            .await;
352
353        // Assert
354        assert!(result.is_err());
355        let err_string = result.err().unwrap().to_string();
356        assert!(err_string.contains(error_message));
357    }
358
359    #[tokio::test]
360    async fn test_query_contract_key_with_missing_path_as_string() {
361        // Arrange
362        let sdk = SDK::new(None, None, None);
363        let verbosity = Some(Verbosity::High);
364        let (rpc_address, _, _, _, _) = get_network_constants();
365
366        let entity = get_entity_input().await;
367        let error_message = "Invalid argument 'Path string': Path string is empty";
368
369        let path = PathIdentifierInput::String("".to_string()); // empty path should error
370
371        // Act
372        let result = sdk
373            .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
374            .await;
375
376        // Assert
377        assert!(result.is_err());
378        let err_string = result.err().unwrap().to_string();
379        assert!(err_string.contains(error_message));
380    }
381
382    #[tokio::test]
383    async fn test_query_contract_key_with_path_as_string() {
384        // Arrange
385        let sdk = SDK::new(None, None, None);
386        let verbosity = Some(Verbosity::High);
387        let (rpc_address, _, _, _, _) = get_network_constants();
388
389        let entity = get_entity_input().await;
390
391        let vec_of_strings = vec!["installer".to_string()];
392        let path = PathIdentifierInput::Path(vec_of_strings.into());
393
394        // Act
395        let result = sdk
396            .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
397            .await;
398
399        // Assert
400        assert!(result.is_ok());
401    }
402
403    #[tokio::test]
404    async fn test_query_contract_key_with_error() {
405        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
406        let block_identifier =
407            BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(1));
408        let error_message = "error sending request";
409        let entity = get_entity_input().await;
410
411        let path = PathIdentifierInput::String("installer".to_string());
412
413        // Act
414        let result = sdk
415            .query_contract_key(Some(entity), None, path, Some(block_identifier), None, None)
416            .await;
417        // Assert
418        assert!(result.is_err());
419        let err_string = result.err().unwrap().to_string();
420        assert!(err_string.contains(error_message));
421    }
422}