casper_rust_wasm_sdk/sdk/rpcs/
query_global_state.rs

1use crate::{
2    types::{
3        digest::Digest, identifier::global_state_identifier::GlobalStateIdentifier, key::Key,
4        path::Path, sdk_error::SdkError, verbosity::Verbosity,
5    },
6    SDK,
7};
8use casper_client::{
9    cli::query_global_state as query_global_state_cli,
10    query_global_state as query_global_state_lib,
11    rpcs::results::QueryGlobalStateResult as _QueryGlobalStateResult, JsonRpcId, SuccessResponse,
12};
13#[cfg(target_arch = "wasm32")]
14use gloo_utils::format::JsValueSerdeExt;
15use rand::Rng;
16use serde::{Deserialize, Serialize};
17use wasm_bindgen::prelude::*;
18
19// Define a struct to wrap the QueryGlobalStateResult
20#[derive(Debug, Deserialize, Clone, Serialize)]
21#[wasm_bindgen]
22pub struct QueryGlobalStateResult(_QueryGlobalStateResult);
23
24impl From<QueryGlobalStateResult> for _QueryGlobalStateResult {
25    fn from(result: QueryGlobalStateResult) -> Self {
26        result.0
27    }
28}
29
30impl From<_QueryGlobalStateResult> for QueryGlobalStateResult {
31    fn from(result: _QueryGlobalStateResult) -> Self {
32        QueryGlobalStateResult(result)
33    }
34}
35
36#[cfg(target_arch = "wasm32")]
37#[wasm_bindgen]
38impl QueryGlobalStateResult {
39    /// Gets the API version as a JsValue.
40    #[wasm_bindgen(getter)]
41    pub fn api_version(&self) -> JsValue {
42        JsValue::from_serde(&self.0.api_version).unwrap()
43    }
44
45    /// Gets the block header as a JsValue.
46    #[wasm_bindgen(getter)]
47    pub fn block_header(&self) -> JsValue {
48        JsValue::from_serde(&self.0.block_header).unwrap()
49    }
50
51    /// Gets the stored value as a JsValue.
52    #[wasm_bindgen(getter)]
53    pub fn stored_value(&self) -> JsValue {
54        JsValue::from_serde(&self.0.stored_value).unwrap()
55    }
56
57    /// Gets the Merkle proof as a string.
58    #[wasm_bindgen(getter)]
59    pub fn merkle_proof(&self) -> String {
60        self.0.merkle_proof.clone()
61    }
62
63    /// Converts the QueryGlobalStateResult to a JsValue.
64    #[wasm_bindgen(js_name = "toJson")]
65    pub fn to_json(&self) -> JsValue {
66        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
67    }
68}
69
70/// Options for the `query_global_state` method.
71#[derive(Debug, Deserialize, Clone, Default, Serialize)]
72#[wasm_bindgen(js_name = "queryGlobalStateOptions", getter_with_clone)]
73pub struct QueryGlobalStateOptions {
74    pub global_state_identifier: Option<GlobalStateIdentifier>,
75    pub state_root_hash_as_string: Option<String>,
76    pub state_root_hash: Option<Digest>,
77    pub maybe_block_id_as_string: Option<String>,
78    pub key_as_string: Option<String>,
79    pub key: Option<Key>,
80    pub path_as_string: Option<String>,
81    pub path: Option<Path>,
82    pub rpc_address: Option<String>,
83    pub verbosity: Option<Verbosity>,
84}
85
86#[cfg(target_arch = "wasm32")]
87#[wasm_bindgen]
88impl SDK {
89    /// Parses query global state options from a JsValue.
90    ///
91    /// # Arguments
92    ///
93    /// * `options` - A JsValue containing query global state options to be parsed.
94    ///
95    /// # Returns
96    ///
97    /// Parsed query global state options as a `QueryGlobalStateOptions` struct.
98    pub fn query_global_state_options(
99        &self,
100        options: JsValue,
101    ) -> Result<QueryGlobalStateOptions, JsError> {
102        options
103            .into_serde::<QueryGlobalStateOptions>()
104            .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
105    }
106
107    /// Retrieves global state information using the provided options.
108    ///
109    /// # Arguments
110    ///
111    /// * `options` - An optional `QueryGlobalStateOptions` struct containing retrieval options.
112    ///
113    /// # Returns
114    ///
115    /// A `Result` containing either a `QueryGlobalStateResult` or a `JsError` in case of an error.
116    ///
117    /// # Errors
118    ///
119    /// Returns a `JsError` if there is an error during the retrieval process.
120    #[wasm_bindgen(js_name = "query_global_state")]
121    pub async fn query_global_state_js_alias(
122        &self,
123        options: Option<QueryGlobalStateOptions>,
124    ) -> Result<QueryGlobalStateResult, JsError> {
125        match self.query_global_state_js_alias_params(options) {
126            Ok(params) => {
127                let result = self.query_global_state(params).await;
128                match result {
129                    Ok(data) => Ok(data.result.into()),
130                    Err(err) => {
131                        let err = &format!("Error occurred with {err:?}");
132                        Err(JsError::new(err))
133                    }
134                }
135            }
136            Err(err) => {
137                let err = &format!("Error building parameters: {err:?}");
138                Err(JsError::new(err))
139            }
140        }
141    }
142}
143
144#[cfg(target_arch = "wasm32")]
145impl SDK {
146    /// Builds parameters for querying global state based on the provided options.
147    ///
148    /// # Arguments
149    ///
150    /// * `options` - An optional `QueryGlobalStateOptions` struct containing retrieval options.
151    ///
152    /// # Returns
153    ///
154    /// A `Result` containing either a `QueryGlobalStateParams` struct or a `SdkError` in case of an error.
155    pub fn query_global_state_js_alias_params(
156        &self,
157        options: Option<QueryGlobalStateOptions>,
158    ) -> Result<QueryGlobalStateParams, SdkError> {
159        let QueryGlobalStateOptions {
160            global_state_identifier,
161            state_root_hash_as_string,
162            state_root_hash,
163            maybe_block_id_as_string,
164            key_as_string,
165            key,
166            path_as_string,
167            path,
168            verbosity,
169            rpc_address,
170        } = options.unwrap_or_default();
171
172        let key = if let Some(key) = key {
173            Some(KeyIdentifierInput::Key(key))
174        } else if let Some(key_as_string) = key_as_string {
175            Some(KeyIdentifierInput::String(key_as_string))
176        } else {
177            let err_msg = "Error: Missing Key as string or Key".to_string();
178            return Err(SdkError::InvalidArgument {
179                context: "query_global_state",
180                error: err_msg,
181            });
182        };
183
184        let maybe_path = if let Some(path) = path {
185            Some(PathIdentifierInput::Path(path))
186        } else if let Some(path_str) = path_as_string {
187            if path_str.is_empty() {
188                None
189            } else {
190                Some(PathIdentifierInput::String(path_str))
191            }
192        } else {
193            None
194        };
195
196        let query_params = if let Some(hash) = state_root_hash {
197            let state_root_hash_str = hash.to_string();
198            QueryGlobalStateParams {
199                key: key.unwrap(),
200                path: maybe_path.clone(),
201                maybe_global_state_identifier: global_state_identifier.clone(),
202                state_root_hash: if state_root_hash_str.is_empty() {
203                    None
204                } else {
205                    Some(state_root_hash_str)
206                },
207                maybe_block_id: None,
208                verbosity,
209                rpc_address,
210            }
211        } else if let Some(hash) = state_root_hash_as_string {
212            let state_root_hash_str = hash.to_string();
213            QueryGlobalStateParams {
214                key: key.unwrap(),
215                path: maybe_path.clone(),
216                maybe_global_state_identifier: global_state_identifier.clone(),
217                state_root_hash: if state_root_hash_str.is_empty() {
218                    None
219                } else {
220                    Some(state_root_hash_str)
221                },
222                maybe_block_id: None,
223                verbosity,
224                rpc_address,
225            }
226        } else if let Some(maybe_block_id_as_string) = maybe_block_id_as_string {
227            QueryGlobalStateParams {
228                key: key.unwrap(),
229                path: maybe_path.clone(),
230                maybe_global_state_identifier: global_state_identifier.clone(),
231                state_root_hash: None,
232                maybe_block_id: Some(maybe_block_id_as_string),
233                verbosity,
234                rpc_address,
235            }
236        } else {
237            QueryGlobalStateParams {
238                key: key.unwrap(),
239                path: maybe_path.clone(),
240                maybe_global_state_identifier: global_state_identifier.clone(),
241                state_root_hash: None,
242                maybe_block_id: None,
243                verbosity,
244                rpc_address,
245            }
246        };
247        Ok(query_params)
248    }
249}
250
251/// Enum to represent input for KeyIdentifier.
252#[derive(Debug, Clone)]
253pub enum KeyIdentifierInput {
254    Key(Key),
255    String(String),
256}
257
258/// Enum to represent input for PathIdentifier.
259#[derive(Debug, Clone)]
260pub enum PathIdentifierInput {
261    Path(Path),
262    String(String),
263}
264
265/// Struct to store parameters for querying global state.
266#[derive(Debug)]
267pub struct QueryGlobalStateParams {
268    pub key: KeyIdentifierInput,
269    pub path: Option<PathIdentifierInput>,
270    pub maybe_global_state_identifier: Option<GlobalStateIdentifier>,
271    pub state_root_hash: Option<String>,
272    pub maybe_block_id: Option<String>,
273    pub rpc_address: Option<String>,
274    pub verbosity: Option<Verbosity>,
275}
276
277impl SDK {
278    /// Retrieves global state information based on the provided parameters.
279    ///
280    /// # Arguments
281    ///
282    /// * `query_params` - A `QueryGlobalStateParams` struct containing query parameters.
283    ///
284    /// # Returns
285    ///
286    /// A `Result` containing either a `SuccessResponse<_QueryGlobalStateResult>` or a `SdkError` in case of an error.
287    pub async fn query_global_state(
288        &self,
289        query_params: QueryGlobalStateParams,
290    ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
291        //log("query_global_state!");
292
293        let QueryGlobalStateParams {
294            key,
295            path,
296            maybe_global_state_identifier,
297            state_root_hash,
298            maybe_block_id,
299            verbosity,
300            rpc_address,
301        } = query_params;
302
303        let key = match key {
304            KeyIdentifierInput::Key(key) => Some(key),
305            KeyIdentifierInput::String(key_string) => Key::from_formatted_str(&key_string).ok(),
306        };
307
308        if key.is_none() {
309            let err = "Error: Missing key from formatted string".to_string();
310            return Err(SdkError::InvalidArgument {
311                context: "query_global_state",
312                error: err,
313            });
314        }
315
316        let path = if let Some(path) = path {
317            let path = match path {
318                PathIdentifierInput::Path(path) => path,
319                PathIdentifierInput::String(path_string) => Path::from(path_string),
320            };
321            Some(path)
322        } else {
323            None
324        };
325
326        let path_str: String = match path.clone() {
327            Some(p) => p.to_string(),
328            None => String::new(),
329        };
330        let random_id = rand::thread_rng().gen::<u64>().to_string();
331        if let Some(maybe_global_state_identifier) = maybe_global_state_identifier {
332            let path = match path {
333                Some(path) if path.is_empty() => Vec::new(),
334                Some(path) => path.into(),
335                None => Vec::new(),
336            };
337            let random_id = JsonRpcId::from(random_id);
338            query_global_state_lib(
339                random_id,
340                &self.get_rpc_address(rpc_address),
341                self.get_verbosity(verbosity).into(),
342                maybe_global_state_identifier.into(),
343                key.unwrap().into(),
344                path,
345            )
346            .await
347            .map_err(SdkError::from)
348        } else if let Some(state_root_hash) = state_root_hash {
349            let random_id = rand::thread_rng().gen::<u64>().to_string();
350            query_global_state_cli(
351                &random_id,
352                &self.get_rpc_address(rpc_address),
353                self.get_verbosity(verbosity).into(),
354                "",
355                &state_root_hash,
356                &key.unwrap().to_formatted_string(),
357                &path_str,
358            )
359            .await
360            .map_err(SdkError::from)
361        } else if let Some(maybe_block_id) = maybe_block_id {
362            let random_id = rand::thread_rng().gen::<u64>().to_string();
363            query_global_state_cli(
364                &random_id,
365                &self.get_rpc_address(rpc_address),
366                self.get_verbosity(verbosity).into(),
367                &maybe_block_id,
368                "",
369                &key.unwrap().to_formatted_string(),
370                &path_str,
371            )
372            .await
373            .map_err(SdkError::from)
374        } else {
375            let state_root_hash = self
376                .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
377                .await;
378
379            let state_root_hash_as_string: String = match state_root_hash {
380                Ok(state_root_hash) => {
381                    let state_root_hash: Digest =
382                        state_root_hash.result.state_root_hash.unwrap().into();
383                    state_root_hash.to_string()
384                }
385                Err(_) => "".to_string(),
386            };
387            let random_id = rand::thread_rng().gen::<u64>().to_string();
388            query_global_state_cli(
389                &random_id,
390                &self.get_rpc_address(rpc_address),
391                self.get_verbosity(verbosity).into(),
392                "",
393                &state_root_hash_as_string,
394                &key.unwrap().to_formatted_string(),
395                &path_str,
396            )
397            .await
398            .map_err(SdkError::from)
399        }
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406    use crate::{helpers::public_key_from_secret_key, types::public_key::PublicKey};
407    use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
408
409    fn get_key_input() -> KeyIdentifierInput {
410        let secret_key = get_user_secret_key(None).unwrap();
411        let account = public_key_from_secret_key(&secret_key).unwrap();
412        let public_key = PublicKey::new(&account).unwrap();
413        KeyIdentifierInput::String(public_key.to_account_hash().to_formatted_string())
414    }
415
416    #[tokio::test]
417    async fn test_query_global_state_with_none_values() {
418        // Arrange
419        let sdk = SDK::new(None, None, None);
420        let error_message = "Failed to parse state identifier";
421
422        // Act
423        let result = sdk
424            .query_global_state(QueryGlobalStateParams {
425                key: get_key_input(),
426                path: None,
427                maybe_global_state_identifier: None,
428                state_root_hash: None,
429                maybe_block_id: None,
430                verbosity: None,
431                rpc_address: None,
432            })
433            .await;
434
435        // Assert
436        assert!(result.is_err());
437        let err_string = result.err().unwrap().to_string();
438        assert!(err_string.contains(error_message));
439    }
440
441    #[tokio::test]
442    async fn test_query_global_state_with_missing_key() {
443        // Arrange
444        let sdk = SDK::new(None, None, None);
445        let error_message =
446            "Invalid argument 'query_global_state': Error: Missing key from formatted string";
447
448        // Act
449        let result = sdk
450            .query_global_state(QueryGlobalStateParams {
451                key: KeyIdentifierInput::String(String::new()),
452                path: None,
453                maybe_global_state_identifier: None,
454                state_root_hash: None,
455                maybe_block_id: None,
456                verbosity: None,
457                rpc_address: None,
458            })
459            .await;
460
461        // Assert
462        assert!(result.is_err());
463        let err_string = result.err().unwrap().to_string();
464        assert!(err_string.contains(error_message));
465    }
466
467    #[tokio::test]
468    async fn test_query_global_state_with_global_state_identifier() {
469        // Arrange
470        let sdk = SDK::new(None, None, None);
471        let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
472        let verbosity = Some(Verbosity::High);
473        let (rpc_address, _, _, _, _) = get_network_constants();
474
475        // Act
476        let result = sdk
477            .query_global_state(QueryGlobalStateParams {
478                key: get_key_input(),
479                path: None,
480                maybe_global_state_identifier: Some(global_state_identifier.clone()),
481                state_root_hash: None,
482                maybe_block_id: None,
483                verbosity,
484                rpc_address: Some(rpc_address),
485            })
486            .await;
487
488        // Assertmake integra
489        assert!(result.is_ok());
490    }
491
492    #[tokio::test]
493    async fn test_query_global_state_with_state_root_hash() {
494        // Arrange
495        let sdk = SDK::new(None, None, None);
496        let verbosity = Some(Verbosity::High);
497        let (rpc_address, _, _, _, _) = get_network_constants();
498        let state_root_hash: Digest = sdk
499            .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
500            .await
501            .unwrap()
502            .result
503            .state_root_hash
504            .unwrap()
505            .into();
506        // Act
507        let result = sdk
508            .query_global_state(QueryGlobalStateParams {
509                key: get_key_input(),
510                path: None,
511                maybe_global_state_identifier: None,
512                state_root_hash: Some(state_root_hash.to_string()),
513                maybe_block_id: None,
514                verbosity,
515                rpc_address: Some(rpc_address),
516            })
517            .await;
518
519        // Assert
520        assert!(result.is_ok());
521    }
522
523    #[tokio::test]
524    async fn test_query_global_state_with_block_id() {
525        // Arrange
526        let sdk = SDK::new(None, None, None);
527        let verbosity = Some(Verbosity::High);
528        let (rpc_address, _, _, _, _) = get_network_constants();
529
530        // Act
531        let result = sdk
532            .query_global_state(QueryGlobalStateParams {
533                key: get_key_input(),
534                path: None,
535                maybe_global_state_identifier: None,
536                state_root_hash: None,
537                maybe_block_id: Some("1".to_string()),
538                verbosity,
539                rpc_address: Some(rpc_address),
540            })
541            .await;
542
543        // Assert
544        assert!(result.is_ok());
545    }
546
547    #[tokio::test]
548    async fn test_query_global_state_with_error() {
549        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
550
551        let error_message = "error sending request";
552        // Act
553        let result = sdk
554            .query_global_state(QueryGlobalStateParams {
555                key: get_key_input(),
556                path: None,
557                maybe_global_state_identifier: None,
558                state_root_hash: Some(
559                    "588ee7aacb2d3d31476a2d2fb7800ced453926024b97788f8d8cc5cd56b45bf0".to_string(),
560                ),
561                maybe_block_id: None,
562                verbosity: None,
563                rpc_address: None,
564            })
565            .await;
566
567        // Assert
568        assert!(result.is_err());
569        let err_string = result.err().unwrap().to_string();
570        assert!(err_string.contains(error_message));
571    }
572}