1use crate::{
2 types::{
3 digest::Digest, identifier::global_state_identifier::GlobalStateIdentifier, key::Key,
4 path::Path, sdk_error::SdkError, stored_value::StoredValue, 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(all(feature = "js", target_arch = "wasm32"))]
14use gloo_utils::format::JsValueSerdeExt;
15use rand::RngExt;
16use serde::{Deserialize, Serialize};
17#[cfg(feature = "js")]
18use wasm_bindgen::prelude::*;
19
20#[derive(Debug, Deserialize, Clone, Serialize)]
22#[cfg_attr(feature = "js", wasm_bindgen)]
23pub struct QueryGlobalStateResult(_QueryGlobalStateResult);
24
25impl From<QueryGlobalStateResult> for _QueryGlobalStateResult {
26 fn from(result: QueryGlobalStateResult) -> Self {
27 result.0
28 }
29}
30
31impl From<_QueryGlobalStateResult> for QueryGlobalStateResult {
32 fn from(result: _QueryGlobalStateResult) -> Self {
33 QueryGlobalStateResult(result)
34 }
35}
36
37impl QueryGlobalStateResult {
38 pub fn stored_value_typed(&self) -> StoredValue {
40 self.0.stored_value.clone().into()
41 }
42}
43
44#[cfg(all(feature = "js", target_arch = "wasm32"))]
45#[cfg_attr(feature = "js", wasm_bindgen)]
46impl QueryGlobalStateResult {
47 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
49 #[cfg(feature = "js")]
50 pub fn api_version(&self) -> JsValue {
51 JsValue::from_serde(&self.0.api_version).unwrap()
52 }
53
54 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
56 #[cfg(feature = "js")]
57 pub fn block_header(&self) -> JsValue {
58 JsValue::from_serde(&self.0.block_header).unwrap()
59 }
60
61 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
63 #[cfg(feature = "js")]
64 pub fn stored_value(&self) -> JsValue {
65 JsValue::from_serde(&self.0.stored_value).unwrap()
66 }
67
68 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "storedValueTyped"))]
70 pub fn stored_value_typed_js(&self) -> StoredValue {
71 self.stored_value_typed()
72 }
73
74 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
76 pub fn merkle_proof(&self) -> String {
77 self.0.merkle_proof.clone()
78 }
79
80 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toJson"))]
82 #[cfg(feature = "js")]
83 pub fn to_json(&self) -> JsValue {
84 JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
85 }
86}
87
88#[derive(Debug, Deserialize, Clone, Default, Serialize)]
90#[cfg_attr(
91 feature = "js",
92 wasm_bindgen(js_name = "queryGlobalStateOptions", getter_with_clone)
93)]
94pub struct QueryGlobalStateOptions {
95 pub global_state_identifier: Option<GlobalStateIdentifier>,
96 pub state_root_hash_as_string: Option<String>,
97 pub state_root_hash: Option<Digest>,
98 pub maybe_block_id_as_string: Option<String>,
99 pub key_as_string: Option<String>,
100 pub key: Option<Key>,
101 pub path_as_string: Option<String>,
102 pub path: Option<Path>,
103 pub rpc_address: Option<String>,
104 pub verbosity: Option<Verbosity>,
105}
106
107#[cfg(all(feature = "js", target_arch = "wasm32"))]
108#[cfg_attr(feature = "js", wasm_bindgen)]
109impl SDK {
110 #[cfg(feature = "js")]
120 pub fn query_global_state_options(
121 &self,
122 options: JsValue,
123 ) -> Result<QueryGlobalStateOptions, JsError> {
124 options
125 .into_serde::<QueryGlobalStateOptions>()
126 .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
127 }
128
129 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_global_state"))]
143 #[cfg(feature = "js")]
144 pub async fn query_global_state_js_alias(
145 &self,
146 options: Option<QueryGlobalStateOptions>,
147 ) -> Result<QueryGlobalStateResult, JsError> {
148 match self.query_global_state_js_alias_params(options) {
149 Ok(params) => {
150 let result = self.query_global_state(params).await;
151 match result {
152 Ok(data) => Ok(data.result.into()),
153 Err(err) => {
154 let err = &format!("Error occurred with {err:?}");
155 Err(JsError::new(err))
156 }
157 }
158 }
159 Err(err) => {
160 let err = &format!("Error building parameters: {err:?}");
161 Err(JsError::new(err))
162 }
163 }
164 }
165}
166
167#[cfg(all(feature = "js", target_arch = "wasm32"))]
168impl SDK {
169 pub fn query_global_state_js_alias_params(
179 &self,
180 options: Option<QueryGlobalStateOptions>,
181 ) -> Result<QueryGlobalStateParams, SdkError> {
182 let QueryGlobalStateOptions {
183 global_state_identifier,
184 state_root_hash_as_string,
185 state_root_hash,
186 maybe_block_id_as_string,
187 key_as_string,
188 key,
189 path_as_string,
190 path,
191 verbosity,
192 rpc_address,
193 } = options.unwrap_or_default();
194
195 let key = if let Some(key) = key {
196 Some(KeyIdentifierInput::Key(key))
197 } else if let Some(key_as_string) = key_as_string {
198 Some(KeyIdentifierInput::String(key_as_string))
199 } else {
200 let err_msg = "Error: Missing Key as string or Key".to_string();
201 return Err(SdkError::InvalidArgument {
202 context: "query_global_state",
203 error: err_msg,
204 });
205 };
206
207 let maybe_path = if let Some(path) = path {
208 Some(PathIdentifierInput::Path(path))
209 } else if let Some(path_str) = path_as_string {
210 if path_str.is_empty() {
211 None
212 } else {
213 Some(PathIdentifierInput::String(path_str))
214 }
215 } else {
216 None
217 };
218
219 let query_params = if let Some(hash) = state_root_hash {
220 let state_root_hash_str = hash.to_string();
221 QueryGlobalStateParams {
222 key: key.unwrap(),
223 path: maybe_path.clone(),
224 maybe_global_state_identifier: global_state_identifier.clone(),
225 state_root_hash: if state_root_hash_str.is_empty() {
226 None
227 } else {
228 Some(state_root_hash_str)
229 },
230 maybe_block_id: None,
231 verbosity,
232 rpc_address,
233 }
234 } else if let Some(hash) = state_root_hash_as_string {
235 let state_root_hash_str = hash.to_string();
236 QueryGlobalStateParams {
237 key: key.unwrap(),
238 path: maybe_path.clone(),
239 maybe_global_state_identifier: global_state_identifier.clone(),
240 state_root_hash: if state_root_hash_str.is_empty() {
241 None
242 } else {
243 Some(state_root_hash_str)
244 },
245 maybe_block_id: None,
246 verbosity,
247 rpc_address,
248 }
249 } else if let Some(maybe_block_id_as_string) = maybe_block_id_as_string {
250 QueryGlobalStateParams {
251 key: key.unwrap(),
252 path: maybe_path.clone(),
253 maybe_global_state_identifier: global_state_identifier.clone(),
254 state_root_hash: None,
255 maybe_block_id: Some(maybe_block_id_as_string),
256 verbosity,
257 rpc_address,
258 }
259 } else {
260 QueryGlobalStateParams {
261 key: key.unwrap(),
262 path: maybe_path.clone(),
263 maybe_global_state_identifier: global_state_identifier.clone(),
264 state_root_hash: None,
265 maybe_block_id: None,
266 verbosity,
267 rpc_address,
268 }
269 };
270 Ok(query_params)
271 }
272}
273
274#[derive(Debug, Clone)]
276pub enum KeyIdentifierInput {
277 Key(Key),
278 String(String),
279}
280
281#[derive(Debug, Clone)]
283pub enum PathIdentifierInput {
284 Path(Path),
285 String(String),
286}
287
288#[derive(Debug)]
290pub struct QueryGlobalStateParams {
291 pub key: KeyIdentifierInput,
292 pub path: Option<PathIdentifierInput>,
293 pub maybe_global_state_identifier: Option<GlobalStateIdentifier>,
294 pub state_root_hash: Option<String>,
295 pub maybe_block_id: Option<String>,
296 pub rpc_address: Option<String>,
297 pub verbosity: Option<Verbosity>,
298}
299
300impl SDK {
301 pub async fn query_global_state(
311 &self,
312 query_params: QueryGlobalStateParams,
313 ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
314 let QueryGlobalStateParams {
317 key,
318 path,
319 maybe_global_state_identifier,
320 state_root_hash,
321 maybe_block_id,
322 verbosity,
323 rpc_address,
324 } = query_params;
325
326 let key = match key {
327 KeyIdentifierInput::Key(key) => Some(key),
328 KeyIdentifierInput::String(key_string) => Key::from_formatted_str(&key_string).ok(),
329 };
330
331 if key.is_none() {
332 let err = "Error: Missing key from formatted string".to_string();
333 return Err(SdkError::InvalidArgument {
334 context: "query_global_state",
335 error: err,
336 });
337 }
338
339 let path = if let Some(path) = path {
340 let path = match path {
341 PathIdentifierInput::Path(path) => path,
342 PathIdentifierInput::String(path_string) => Path::from(path_string),
343 };
344 Some(path)
345 } else {
346 None
347 };
348
349 let path_str: String = match path.clone() {
350 Some(p) => p.to_string(),
351 None => String::new(),
352 };
353 let random_id = rand::rng().random::<u64>().to_string();
354 if let Some(maybe_global_state_identifier) = maybe_global_state_identifier {
355 let path = match path {
356 Some(path) if path.is_empty() => Vec::new(),
357 Some(path) => path.into(),
358 None => Vec::new(),
359 };
360 let random_id = JsonRpcId::from(random_id);
361 query_global_state_lib(
362 random_id,
363 &self.get_rpc_address(rpc_address),
364 self.get_verbosity(verbosity).into(),
365 Some(maybe_global_state_identifier.into()),
366 key.unwrap().into(),
367 path,
368 )
369 .await
370 .map_err(SdkError::from)
371 } else if let Some(state_root_hash) = state_root_hash {
372 let random_id = rand::rng().random::<u64>().to_string();
373 query_global_state_cli(
374 &random_id,
375 &self.get_rpc_address(rpc_address),
376 self.get_verbosity(verbosity).into(),
377 "",
378 &state_root_hash,
379 &key.unwrap().to_formatted_string(),
380 &path_str,
381 )
382 .await
383 .map_err(SdkError::from)
384 } else if let Some(maybe_block_id) = maybe_block_id {
385 let random_id = rand::rng().random::<u64>().to_string();
386 query_global_state_cli(
387 &random_id,
388 &self.get_rpc_address(rpc_address),
389 self.get_verbosity(verbosity).into(),
390 &maybe_block_id,
391 "",
392 &key.unwrap().to_formatted_string(),
393 &path_str,
394 )
395 .await
396 .map_err(SdkError::from)
397 } else {
398 let state_root_hash = self
399 .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
400 .await;
401
402 let state_root_hash_as_string: String = match state_root_hash {
403 Ok(state_root_hash) => {
404 let state_root_hash: Digest =
405 state_root_hash.result.state_root_hash.unwrap().into();
406 state_root_hash.to_string()
407 }
408 Err(_) => "".to_string(),
409 };
410 let random_id = rand::rng().random::<u64>().to_string();
411 query_global_state_cli(
412 &random_id,
413 &self.get_rpc_address(rpc_address),
414 self.get_verbosity(verbosity).into(),
415 "",
416 &state_root_hash_as_string,
417 &key.unwrap().to_formatted_string(),
418 &path_str,
419 )
420 .await
421 .map_err(SdkError::from)
422 }
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::{helpers::public_key_from_secret_key, types::public_key::PublicKey};
430 use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
431
432 fn get_key_input() -> KeyIdentifierInput {
433 let secret_key = get_user_secret_key(None).unwrap();
434 let account = public_key_from_secret_key(&secret_key).unwrap();
435 let public_key = PublicKey::new(&account).unwrap();
436 KeyIdentifierInput::String(public_key.to_account_hash().to_formatted_string())
437 }
438
439 #[tokio::test]
440 async fn test_query_global_state_with_none_values() {
441 let sdk = SDK::new(None, None, None);
443 let error_message = "failed to parse";
444
445 let result = sdk
447 .query_global_state(QueryGlobalStateParams {
448 key: get_key_input(),
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!(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_missing_key() {
466 let sdk = SDK::new(None, None, None);
468 let error_message =
469 "Invalid argument 'query_global_state': Error: Missing key from formatted string";
470
471 let result = sdk
473 .query_global_state(QueryGlobalStateParams {
474 key: KeyIdentifierInput::String(String::new()),
475 path: None,
476 maybe_global_state_identifier: None,
477 state_root_hash: None,
478 maybe_block_id: None,
479 verbosity: None,
480 rpc_address: None,
481 })
482 .await;
483
484 assert!(result.is_err());
486 let err_string = result.err().unwrap().to_string();
487 assert!(err_string.contains(error_message));
488 }
489
490 #[tokio::test]
491 async fn test_query_global_state_with_global_state_identifier() {
492 let sdk = SDK::new(None, None, None);
494 let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
495 let verbosity = Some(Verbosity::High);
496 let (rpc_address, _, _, _, _) = get_network_constants();
497
498 let result = sdk
500 .query_global_state(QueryGlobalStateParams {
501 key: get_key_input(),
502 path: None,
503 maybe_global_state_identifier: Some(global_state_identifier.clone()),
504 state_root_hash: None,
505 maybe_block_id: None,
506 verbosity,
507 rpc_address: Some(rpc_address),
508 })
509 .await;
510
511 assert!(result.is_ok());
513 }
514
515 #[tokio::test]
516 async fn test_query_global_state_with_state_root_hash() {
517 let sdk = SDK::new(None, None, None);
519 let verbosity = Some(Verbosity::High);
520 let (rpc_address, _, _, _, _) = get_network_constants();
521 let state_root_hash: Digest = sdk
522 .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
523 .await
524 .unwrap()
525 .result
526 .state_root_hash
527 .unwrap()
528 .into();
529 let result = sdk
531 .query_global_state(QueryGlobalStateParams {
532 key: get_key_input(),
533 path: None,
534 maybe_global_state_identifier: None,
535 state_root_hash: Some(state_root_hash.to_string()),
536 maybe_block_id: None,
537 verbosity,
538 rpc_address: Some(rpc_address),
539 })
540 .await;
541
542 assert!(result.is_ok());
544 }
545
546 #[tokio::test]
547 async fn test_query_global_state_with_block_id() {
548 let sdk = SDK::new(None, None, None);
550 let verbosity = Some(Verbosity::High);
551 let (rpc_address, _, _, _, _) = get_network_constants();
552
553 let result = sdk
555 .query_global_state(QueryGlobalStateParams {
556 key: get_key_input(),
557 path: None,
558 maybe_global_state_identifier: None,
559 state_root_hash: None,
560 maybe_block_id: Some("1".to_string()),
561 verbosity,
562 rpc_address: Some(rpc_address),
563 })
564 .await;
565
566 assert!(result.is_ok());
568 }
569
570 #[tokio::test]
571 async fn test_query_global_state_typed_account_access() {
572 let sdk = SDK::new(None, None, None);
573 let verbosity = Some(Verbosity::High);
574 let (rpc_address, _, _, _, _) = get_network_constants();
575 let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
576
577 let result = sdk
578 .query_global_state(QueryGlobalStateParams {
579 key: get_key_input(),
580 path: None,
581 maybe_global_state_identifier: Some(global_state_identifier),
582 state_root_hash: None,
583 maybe_block_id: None,
584 verbosity,
585 rpc_address: Some(rpc_address),
586 })
587 .await
588 .expect("query_global_state");
589
590 let stored = StoredValue::from(result.result.stored_value.clone());
591 assert_eq!(stored.variant(), "Account");
592 let account = stored.as_account().expect("typed Account");
593 assert!(!account.account_hash().to_formatted_string().is_empty());
594 assert!(!account.main_purse().to_formatted_string().is_empty());
595 let json = serde_json::to_value(&result.result.stored_value).expect("json");
597 assert!(json.get("Account").is_some());
598 }
599}