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
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            query_global_state_lib(
338                JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
339                &self.get_rpc_address(rpc_address),
340                self.get_verbosity(verbosity).into(),
341                maybe_global_state_identifier.into(),
342                key.unwrap().into(),
343                path,
344            )
345            .await
346            .map_err(SdkError::from)
347        } else if let Some(state_root_hash) = state_root_hash {
348            query_global_state_cli(
349                &rand::thread_rng().gen::<u64>().to_string(),
350                &self.get_rpc_address(rpc_address),
351                self.get_verbosity(verbosity).into(),
352                "",
353                &state_root_hash,
354                &key.unwrap().to_formatted_string(),
355                &path_str,
356            )
357            .await
358            .map_err(SdkError::from)
359        } else if let Some(maybe_block_id) = maybe_block_id {
360            query_global_state_cli(
361                &rand::thread_rng().gen::<u64>().to_string(),
362                &self.get_rpc_address(rpc_address),
363                self.get_verbosity(verbosity).into(),
364                &maybe_block_id,
365                "",
366                &key.unwrap().to_formatted_string(),
367                &path_str,
368            )
369            .await
370            .map_err(SdkError::from)
371        } else {
372            let state_root_hash = self
373                .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
374                .await;
375
376            let state_root_hash_as_string: String = match state_root_hash {
377                Ok(state_root_hash) => {
378                    let state_root_hash: Digest =
379                        state_root_hash.result.state_root_hash.unwrap().into();
380                    state_root_hash.to_string()
381                }
382                Err(_) => "".to_string(),
383            };
384
385            query_global_state_cli(
386                &rand::thread_rng().gen::<u64>().to_string(),
387                &self.get_rpc_address(rpc_address),
388                self.get_verbosity(verbosity).into(),
389                "",
390                &state_root_hash_as_string,
391                &key.unwrap().to_formatted_string(),
392                &path_str,
393            )
394            .await
395            .map_err(SdkError::from)
396        }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use crate::{helpers::public_key_from_secret_key, types::public_key::PublicKey};
404    use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
405
406    fn get_key_input() -> KeyIdentifierInput {
407        let secret_key = get_user_secret_key(None).unwrap();
408        let account = public_key_from_secret_key(&secret_key).unwrap();
409        let public_key = PublicKey::new(&account).unwrap();
410        KeyIdentifierInput::String(public_key.to_account_hash().to_formatted_string())
411    }
412
413    #[tokio::test]
414    async fn test_query_global_state_with_none_values() {
415        // Arrange
416        let sdk = SDK::new(None, None, None);
417        let error_message = "Failed to parse state identifier";
418
419        // Act
420        let result = sdk
421            .query_global_state(QueryGlobalStateParams {
422                key: get_key_input(),
423                path: None,
424                maybe_global_state_identifier: None,
425                state_root_hash: None,
426                maybe_block_id: None,
427                verbosity: None,
428                rpc_address: None,
429            })
430            .await;
431
432        // Assert
433        assert!(result.is_err());
434        let err_string = result.err().unwrap().to_string();
435        assert!(err_string.contains(error_message));
436    }
437
438    #[tokio::test]
439    async fn test_query_global_state_with_missing_key() {
440        // Arrange
441        let sdk = SDK::new(None, None, None);
442        let error_message =
443            "Invalid argument 'query_global_state': Error: Missing key from formatted string";
444
445        // Act
446        let result = sdk
447            .query_global_state(QueryGlobalStateParams {
448                key: KeyIdentifierInput::String(String::new()),
449                path: None,
450                maybe_global_state_identifier: None,
451                state_root_hash: None,
452                maybe_block_id: None,
453                verbosity: None,
454                rpc_address: None,
455            })
456            .await;
457
458        // Assert
459        assert!(result.is_err());
460        let err_string = result.err().unwrap().to_string();
461        assert!(err_string.contains(error_message));
462    }
463
464    #[tokio::test]
465    async fn test_query_global_state_with_global_state_identifier() {
466        // Arrange
467        let sdk = SDK::new(None, None, None);
468        let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
469        let verbosity = Some(Verbosity::High);
470        let (rpc_address, _, _, _, _) = get_network_constants();
471
472        // Act
473        let result = sdk
474            .query_global_state(QueryGlobalStateParams {
475                key: get_key_input(),
476                path: None,
477                maybe_global_state_identifier: Some(global_state_identifier.clone()),
478                state_root_hash: None,
479                maybe_block_id: None,
480                verbosity,
481                rpc_address: Some(rpc_address),
482            })
483            .await;
484
485        // Assertmake integra
486        assert!(result.is_ok());
487    }
488
489    #[tokio::test]
490    async fn test_query_global_state_with_state_root_hash() {
491        // Arrange
492        let sdk = SDK::new(None, None, None);
493        let verbosity = Some(Verbosity::High);
494        let (rpc_address, _, _, _, _) = get_network_constants();
495        let state_root_hash: Digest = sdk
496            .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
497            .await
498            .unwrap()
499            .result
500            .state_root_hash
501            .unwrap()
502            .into();
503        // Act
504        let result = sdk
505            .query_global_state(QueryGlobalStateParams {
506                key: get_key_input(),
507                path: None,
508                maybe_global_state_identifier: None,
509                state_root_hash: Some(state_root_hash.to_string()),
510                maybe_block_id: None,
511                verbosity,
512                rpc_address: Some(rpc_address),
513            })
514            .await;
515
516        // Assert
517        assert!(result.is_ok());
518    }
519
520    #[tokio::test]
521    async fn test_query_global_state_with_block_id() {
522        // Arrange
523        let sdk = SDK::new(None, None, None);
524        let verbosity = Some(Verbosity::High);
525        let (rpc_address, _, _, _, _) = get_network_constants();
526
527        // Act
528        let result = sdk
529            .query_global_state(QueryGlobalStateParams {
530                key: get_key_input(),
531                path: None,
532                maybe_global_state_identifier: None,
533                state_root_hash: None,
534                maybe_block_id: Some("1".to_string()),
535                verbosity,
536                rpc_address: Some(rpc_address),
537            })
538            .await;
539
540        // Assert
541        assert!(result.is_ok());
542    }
543
544    #[tokio::test]
545    async fn test_query_global_state_with_error() {
546        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
547
548        let error_message = "error sending request for url (http://localhost/rpc)";
549        // Act
550        let result = sdk
551            .query_global_state(QueryGlobalStateParams {
552                key: get_key_input(),
553                path: None,
554                maybe_global_state_identifier: None,
555                state_root_hash: Some(
556                    "588ee7aacb2d3d31476a2d2fb7800ced453926024b97788f8d8cc5cd56b45bf0".to_string(),
557                ),
558                maybe_block_id: None,
559                verbosity: None,
560                rpc_address: None,
561            })
562            .await;
563
564        // Assert
565        assert!(result.is_err());
566        let err_string = result.err().unwrap().to_string();
567        assert!(err_string.contains(error_message));
568    }
569}