casper_rust_wasm_sdk/sdk/contract/
query_contract_key.rs

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