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#[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 #[wasm_bindgen(getter)]
41 pub fn api_version(&self) -> JsValue {
42 JsValue::from_serde(&self.0.api_version).unwrap()
43 }
44
45 #[wasm_bindgen(getter)]
47 pub fn block_header(&self) -> JsValue {
48 JsValue::from_serde(&self.0.block_header).unwrap()
49 }
50
51 #[wasm_bindgen(getter)]
53 pub fn stored_value(&self) -> JsValue {
54 JsValue::from_serde(&self.0.stored_value).unwrap()
55 }
56
57 #[wasm_bindgen(getter)]
59 pub fn merkle_proof(&self) -> String {
60 self.0.merkle_proof.clone()
61 }
62
63 #[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#[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 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 #[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 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#[derive(Debug, Clone)]
253pub enum KeyIdentifierInput {
254 Key(Key),
255 String(String),
256}
257
258#[derive(Debug, Clone)]
260pub enum PathIdentifierInput {
261 Path(Path),
262 String(String),
263}
264
265#[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 pub async fn query_global_state(
288 &self,
289 query_params: QueryGlobalStateParams,
290 ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
291 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 let sdk = SDK::new(None, None, None);
420 let error_message = "Failed to parse state identifier";
421
422 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!(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 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 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!(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 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 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 assert!(result.is_ok());
490 }
491
492 #[tokio::test]
493 async fn test_query_global_state_with_state_root_hash() {
494 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 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!(result.is_ok());
521 }
522
523 #[tokio::test]
524 async fn test_query_global_state_with_block_id() {
525 let sdk = SDK::new(None, None, None);
527 let verbosity = Some(Verbosity::High);
528 let (rpc_address, _, _, _, _) = get_network_constants();
529
530 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!(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 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!(result.is_err());
569 let err_string = result.err().unwrap().to_string();
570 assert!(err_string.contains(error_message));
571 }
572}