1#[cfg(all(feature = "js", target_arch = "wasm32"))]
2use crate::types::identifier::block_identifier::BlockIdentifier;
3#[cfg(all(feature = "js", target_arch = "wasm32"))]
4use crate::{rpcs::query_global_state::QueryGlobalStateResult, types::path::Path};
5use crate::{
6 rpcs::query_global_state::{KeyIdentifierInput, PathIdentifierInput, QueryGlobalStateParams},
7 types::{
8 identifier::{block_identifier::BlockIdentifierInput, entity_identifier::EntityIdentifier},
9 sdk_error::SdkError,
10 verbosity::Verbosity,
11 },
12 SDK,
13};
14use casper_client::{
15 rpcs::results::QueryGlobalStateResult as _QueryGlobalStateResult, SuccessResponse,
16};
17#[cfg(all(feature = "js", target_arch = "wasm32"))]
18use gloo_utils::format::JsValueSerdeExt;
19#[cfg(all(feature = "js", target_arch = "wasm32"))]
20use serde::{Deserialize, Serialize};
21#[cfg(all(feature = "js", target_arch = "wasm32"))]
22use wasm_bindgen::prelude::*;
23
24#[derive(Deserialize, Default, Serialize)]
25#[cfg(all(feature = "js", target_arch = "wasm32"))]
26#[cfg_attr(
27 feature = "js",
28 wasm_bindgen(js_name = "queryContractKeyOptions", getter_with_clone)
29)]
30pub struct QueryContractKeyOptions {
31 pub entity_identifier: Option<EntityIdentifier>,
32 pub entity_identifier_as_string: Option<String>,
33 pub maybe_block_identifier: Option<BlockIdentifier>,
34 pub maybe_block_id_as_string: Option<String>,
35 pub path_as_string: Option<String>,
36 pub path: Option<Path>,
37 pub rpc_address: Option<String>,
38 pub verbosity: Option<Verbosity>,
39}
40
41#[cfg(all(feature = "js", target_arch = "wasm32"))]
42#[cfg_attr(feature = "js", wasm_bindgen)]
43impl SDK {
44 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_contract_key_options"))]
46 #[cfg(feature = "js")]
47 pub fn query_contract_key_state_options(
48 &self,
49 options: JsValue,
50 ) -> Result<QueryContractKeyOptions, JsError> {
51 options
52 .into_serde::<QueryContractKeyOptions>()
53 .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
54 }
55
56 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "query_contract_key"))]
58 #[cfg(feature = "js")]
59 pub async fn query_contract_key_js_alias(
60 &self,
61 options: Option<QueryContractKeyOptions>,
62 ) -> Result<QueryGlobalStateResult, JsError> {
63 let options = options.unwrap_or_default();
64
65 let path_input = match (options.path, options.path_as_string) {
67 (Some(path), None) => PathIdentifierInput::Path(path),
68 (None, Some(path_string)) => PathIdentifierInput::String(path_string),
69 (Some(_), Some(_)) => {
70 let err = "Only one of `path` or `path_as_string` can be provided".to_string();
71 return Err(JsError::new(&err));
72 }
73 (None, None) => {
74 let err = "Either `path` or `path_as_string` must be provided".to_string();
75 return Err(JsError::new(&err));
76 }
77 };
78
79 let maybe_block_identifier =
80 if let Some(maybe_block_identifier) = options.maybe_block_identifier {
81 Some(BlockIdentifierInput::BlockIdentifier(
82 maybe_block_identifier,
83 ))
84 } else {
85 options
86 .maybe_block_id_as_string
87 .map(BlockIdentifierInput::String)
88 };
89
90 let result = self
91 .query_contract_key(
92 options.entity_identifier,
93 options.entity_identifier_as_string,
94 path_input,
95 maybe_block_identifier,
96 options.verbosity,
97 options.rpc_address,
98 )
99 .await;
100 match result {
101 Ok(data) => Ok(data.result.into()),
102 Err(err) => {
103 let err = &format!("Error occurred with {err:?}");
104 Err(JsError::new(err))
105 }
106 }
107 }
108}
109
110impl SDK {
112 pub async fn query_contract_key(
122 &self,
123 entity_identifier: Option<EntityIdentifier>,
124 entity_identifier_as_string: Option<String>,
125 path: PathIdentifierInput,
126 maybe_block_identifier: Option<BlockIdentifierInput>,
127 verbosity: Option<Verbosity>,
128 rpc_address: Option<String>,
129 ) -> Result<SuccessResponse<_QueryGlobalStateResult>, SdkError> {
130 match path {
131 PathIdentifierInput::Path(ref path_struct) => {
132 if path_struct.is_empty() {
133 return Err(SdkError::InvalidArgument {
134 context: "Path",
135 error: "Path is empty".to_string(),
136 });
137 }
138 path_struct.to_string()
139 }
140 PathIdentifierInput::String(ref path_string) => {
141 if path_string.is_empty() {
142 return Err(SdkError::InvalidArgument {
143 context: "Path string",
144 error: "Path string is empty".to_string(),
145 });
146 }
147 path_string.clone()
148 }
149 };
150
151 let entity = self
153 .get_entity(
154 entity_identifier.clone(),
155 entity_identifier_as_string.clone(),
156 maybe_block_identifier.clone(),
157 verbosity,
158 rpc_address.clone(),
159 )
160 .await;
161
162 let key_as_string = entity_identifier_as_string
163 .or_else(|| entity_identifier.as_ref().map(ToString::to_string))
164 .unwrap_or_default();
165
166 let maybe_block_id: Option<String> = match maybe_block_identifier.clone() {
167 Some(block_identifier) => match block_identifier {
168 BlockIdentifierInput::BlockIdentifier(_) => {
169 maybe_block_identifier.map(|b| b.to_string())
170 }
171 BlockIdentifierInput::String(block_identifier_as_string) => {
172 Some(block_identifier_as_string.clone())
173 }
174 },
175 None => None,
176 };
177
178 match entity {
179 Ok(_) => {
181 let key = KeyIdentifierInput::String(key_as_string);
182 self.query_global_state(QueryGlobalStateParams {
183 key,
184 path: Some(path),
185 maybe_global_state_identifier: None,
186 state_root_hash: None,
187 maybe_block_id,
188 verbosity,
189 rpc_address,
190 })
191 .await
192 }
193 Err(_) => {
194 let key_as_string =
196 crate::helpers::contract_hash_key_for_global_state(&key_as_string);
197 let key = KeyIdentifierInput::String(key_as_string);
198 self.query_global_state(QueryGlobalStateParams {
199 key,
200 path: Some(path),
201 maybe_global_state_identifier: None,
202 state_root_hash: None,
203 maybe_block_id,
204 verbosity,
205 rpc_address,
206 })
207 .await
208 }
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::{
217 install_cep78,
218 types::{
219 identifier::{block_identifier::BlockIdentifier, entity_identifier::EntityIdentifier},
220 verbosity::Verbosity,
221 },
222 };
223 use sdk_tests::tests::helpers::{
224 get_block, get_enable_addressable_entity, get_network_constants,
225 };
226 use tokio;
227
228 async fn get_entity_input() -> EntityIdentifier {
229 EntityIdentifier::from_formatted_str(&install_cep78().await).unwrap()
230 }
231
232 #[tokio::test]
233 async fn test_query_contract_key_with_none_values() {
234 let sdk = SDK::new(None, None, None);
236 let error_message = "Invalid argument 'Path string': Path string is empty";
237
238 let path = PathIdentifierInput::String("".to_string());
239
240 let result = sdk
242 .query_contract_key(Some(get_entity_input().await), None, path, None, None, None)
243 .await;
244
245 assert!(result.is_err());
247 let err_string = result.err().unwrap().to_string();
248 assert!(err_string.contains(error_message));
249 }
250
251 #[tokio::test]
252 async fn test_query_contract_key_with_missing_key() {
253 let sdk = SDK::new(None, None, None);
255 let error_message = if get_enable_addressable_entity() {
256 "Invalid argument 'get_entity': Error: Missing entity identifier"
257 } else {
258 "Invalid argument 'query_global_state': Error: Missing key from formatted string"
259 };
260
261 let path = PathIdentifierInput::String("installer".to_string());
262
263 let result = sdk
265 .query_contract_key(None, None, path, None, None, None)
266 .await;
267
268 assert!(result.is_err());
270 let err_string = result.err().unwrap().to_string();
271 assert!(err_string.contains(error_message));
272 }
273
274 #[tokio::test]
275 async fn test_query_contract_key_with_entity_identifier_as_string() {
276 let sdk = SDK::new(None, None, None);
278 let verbosity = Some(Verbosity::High);
279 let (rpc_address, _, _, _, _) = get_network_constants();
280
281 let entity = get_entity_input().await;
282
283 let path = PathIdentifierInput::String("installer".to_string());
284
285 let (_, block_height) = get_block(&rpc_address.clone()).await;
286 let block_identifier =
287 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
288
289 let result = sdk
291 .query_contract_key(
292 Some(entity),
293 None,
294 path,
295 Some(block_identifier),
296 verbosity,
297 Some(rpc_address),
298 )
299 .await;
300
301 assert!(result.is_ok());
303 }
304
305 #[tokio::test]
306 async fn test_query_contract_key_with_global_state_identifier() {
307 let sdk = SDK::new(None, None, None);
309 let verbosity = Some(Verbosity::High);
310 let (rpc_address, _, _, _, _) = get_network_constants();
311
312 let entity = get_entity_input().await;
313
314 let path = PathIdentifierInput::String("installer".to_string());
315
316 let (_, block_height) = get_block(&rpc_address.clone()).await;
317 let block_identifier =
318 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(block_height));
319 let result = sdk
321 .query_contract_key(
322 None,
323 Some(entity.to_string()),
324 path,
325 Some(block_identifier),
326 verbosity,
327 Some(rpc_address),
328 )
329 .await;
330
331 assert!(result.is_ok());
333 }
334
335 #[tokio::test]
336 async fn test_query_contract_key_with_missing_path() {
337 let sdk = SDK::new(None, None, None);
339 let verbosity = Some(Verbosity::High);
340 let (rpc_address, _, _, _, _) = get_network_constants();
341
342 let entity = get_entity_input().await;
343 let error_message = "Invalid argument 'Path': Path is empty";
344
345 let vec_of_strings = vec!["".to_string()]; let path = PathIdentifierInput::Path(vec_of_strings.into());
347
348 let result = sdk
350 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
351 .await;
352
353 assert!(result.is_err());
355 let err_string = result.err().unwrap().to_string();
356 assert!(err_string.contains(error_message));
357 }
358
359 #[tokio::test]
360 async fn test_query_contract_key_with_missing_path_as_string() {
361 let sdk = SDK::new(None, None, None);
363 let verbosity = Some(Verbosity::High);
364 let (rpc_address, _, _, _, _) = get_network_constants();
365
366 let entity = get_entity_input().await;
367 let error_message = "Invalid argument 'Path string': Path string is empty";
368
369 let path = PathIdentifierInput::String("".to_string()); let result = sdk
373 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
374 .await;
375
376 assert!(result.is_err());
378 let err_string = result.err().unwrap().to_string();
379 assert!(err_string.contains(error_message));
380 }
381
382 #[tokio::test]
383 async fn test_query_contract_key_with_path_as_string() {
384 let sdk = SDK::new(None, None, None);
386 let verbosity = Some(Verbosity::High);
387 let (rpc_address, _, _, _, _) = get_network_constants();
388
389 let entity = get_entity_input().await;
390
391 let vec_of_strings = vec!["installer".to_string()];
392 let path = PathIdentifierInput::Path(vec_of_strings.into());
393
394 let result = sdk
396 .query_contract_key(Some(entity), None, path, None, verbosity, Some(rpc_address))
397 .await;
398
399 assert!(result.is_ok());
401 }
402
403 #[tokio::test]
404 async fn test_query_contract_key_with_error() {
405 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
406 let block_identifier =
407 BlockIdentifierInput::BlockIdentifier(BlockIdentifier::from_height(1));
408 let error_message = "error sending request";
409 let entity = get_entity_input().await;
410
411 let path = PathIdentifierInput::String("installer".to_string());
412
413 let result = sdk
415 .query_contract_key(Some(entity), None, path, Some(block_identifier), None, None)
416 .await;
417 assert!(result.is_err());
419 let err_string = result.err().unwrap().to_string();
420 assert!(err_string.contains(error_message));
421 }
422}