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
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 let sdk = SDK::new(None, None, None);
417 let error_message = "Failed to parse state identifier";
418
419 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!(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 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 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!(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 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 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 assert!(result.is_ok());
487 }
488
489 #[tokio::test]
490 async fn test_query_global_state_with_state_root_hash() {
491 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 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!(result.is_ok());
518 }
519
520 #[tokio::test]
521 async fn test_query_global_state_with_block_id() {
522 let sdk = SDK::new(None, None, None);
524 let verbosity = Some(Verbosity::High);
525 let (rpc_address, _, _, _, _) = get_network_constants();
526
527 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!(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 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!(result.is_err());
566 let err_string = result.err().unwrap().to_string();
567 assert!(err_string.contains(error_message));
568 }
569}