1use crate::errors::Error;
4use params::Param;
5use scale_value::{Composite, ValueDef, stringify::custom_parsers};
6use std::fmt::{Display, Formatter, Write};
7use subxt::{
8 Metadata, OnlineClient, SubstrateConfig,
9 dynamic::Value,
10 ext::futures::TryStreamExt,
11 metadata::types::{PalletMetadata, StorageEntryType},
12 utils::to_hex,
13};
14
15pub mod action;
16pub mod params;
17
18pub type RawValue = Value<u32>;
19
20fn format_single_tuples<T, W: Write>(value: &Value<T>, mut writer: W) -> Option<core::fmt::Result> {
21 if let ValueDef::Composite(Composite::Unnamed(vals)) = &value.value &&
22 vals.len() == 1
23 {
24 let val = &vals[0];
25 return match raw_value_to_string(val, "") {
26 Ok(r) => match writer.write_str(&r) {
27 Ok(_) => Some(Ok(())),
28 Err(_) => None,
29 },
30 Err(_) => None,
31 }
32 }
33 None
34}
35
36fn format_hex<T, W: Write>(value: &Value<T>, mut writer: W) -> Option<core::fmt::Result> {
38 let mut result = String::new();
39 match scale_value::stringify::custom_formatters::format_hex(value, &mut result) {
40 Some(res) => match res {
41 Ok(_) => match writer.write_str(&result.to_lowercase()) {
42 Ok(_) => Some(Ok(())),
43 Err(_) => None,
44 },
45 Err(_) => None,
46 },
47 None => None,
48 }
49}
50
51pub fn raw_value_to_string<T>(value: &Value<T>, indent: &str) -> anyhow::Result<String> {
65 let mut result = String::new();
66 scale_value::stringify::to_writer_custom()
67 .compact()
68 .pretty()
69 .add_custom_formatter(|v, w| format_hex(v, w))
70 .add_custom_formatter(|v, w| format_single_tuples(v, w))
71 .write(value, &mut result)?;
72
73 let indented = result
75 .lines()
76 .map(|line| format!("{indent}{line}"))
77 .collect::<Vec<_>>()
78 .join("\n");
79 Ok(indented)
80}
81
82pub fn render_storage_key_values(
97 key_value_pairs: &[(Vec<Value>, RawValue)],
98) -> anyhow::Result<String> {
99 let mut result = String::new();
100 let indent = " ";
101 for (keys, value) in key_value_pairs {
102 result.push_str("[\n");
103 if !keys.is_empty() {
104 for key in keys {
105 result.push_str(&raw_value_to_string(key, indent)?);
106 result.push_str(",\n");
107 }
108 }
109 result.push_str(&raw_value_to_string(value, indent)?);
110 result.push_str("\n]\n");
111 }
112 Ok(result)
113}
114
115#[derive(Clone, Debug, Eq, PartialEq)]
117pub enum CallItem {
118 Function(Function),
120 Constant(Constant),
122 Storage(Storage),
124}
125
126impl Default for CallItem {
127 fn default() -> Self {
128 Self::Function(Function::default())
129 }
130}
131
132impl Display for CallItem {
133 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
134 match self {
135 CallItem::Function(function) => function.fmt(f),
136 CallItem::Constant(constant) => constant.fmt(f),
137 CallItem::Storage(storage) => storage.fmt(f),
138 }
139 }
140}
141
142impl CallItem {
143 pub fn as_function(&self) -> Option<&Function> {
145 match self {
146 CallItem::Function(f) => Some(f),
147 _ => None,
148 }
149 }
150
151 pub fn as_constant(&self) -> Option<&Constant> {
153 match self {
154 CallItem::Constant(c) => Some(c),
155 _ => None,
156 }
157 }
158
159 pub fn as_storage(&self) -> Option<&Storage> {
161 match self {
162 CallItem::Storage(s) => Some(s),
163 _ => None,
164 }
165 }
166
167 pub fn name(&self) -> &str {
169 match self {
170 CallItem::Function(function) => &function.name,
171 CallItem::Constant(constant) => &constant.name,
172 CallItem::Storage(storage) => &storage.name,
173 }
174 }
175 pub fn hint(&self) -> &str {
177 match self {
178 CallItem::Function(_) => "📝 [EXTRINSIC]",
179 CallItem::Constant(_) => "[CONSTANT]",
180 CallItem::Storage(_) => "[STORAGE]",
181 }
182 }
183
184 pub fn docs(&self) -> &str {
186 match self {
187 CallItem::Function(function) => &function.docs,
188 CallItem::Constant(constant) => &constant.docs,
189 CallItem::Storage(storage) => &storage.docs,
190 }
191 }
192
193 pub fn pallet(&self) -> &str {
195 match self {
196 CallItem::Function(function) => &function.pallet,
197 CallItem::Constant(constant) => &constant.pallet,
198 CallItem::Storage(storage) => &storage.pallet,
199 }
200 }
201}
202
203#[derive(Clone, Debug, Default, Eq, PartialEq)]
205pub struct Pallet {
206 pub name: String,
208 pub index: u8,
210 pub docs: String,
212 pub functions: Vec<Function>,
214 pub constants: Vec<Constant>,
216 pub state: Vec<Storage>,
218}
219
220impl Display for Pallet {
221 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
222 write!(f, "{}", self.name)
223 }
224}
225
226impl Pallet {
227 pub fn get_all_callables(&self) -> Vec<CallItem> {
238 let mut callables = Vec::new();
239 for function in &self.functions {
240 callables.push(CallItem::Function(function.clone()));
241 }
242 for constant in &self.constants {
243 callables.push(CallItem::Constant(constant.clone()));
244 }
245 for storage in &self.state {
246 callables.push(CallItem::Storage(storage.clone()));
247 }
248 callables
249 }
250}
251
252#[derive(Clone, Debug, Default, Eq, PartialEq)]
254pub struct Function {
255 pub pallet: String,
257 pub name: String,
259 pub index: u8,
261 pub docs: String,
263 pub params: Vec<Param>,
265 pub is_supported: bool,
267}
268
269impl Display for Function {
270 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
271 write!(f, "{}", self.name)
272 }
273}
274
275#[derive(Clone, Debug, Eq, PartialEq)]
277pub struct Constant {
278 pub pallet: String,
280 pub name: String,
282 pub docs: String,
284 pub value: RawValue,
286}
287
288impl Display for Constant {
289 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
290 write!(f, "{}", self.name)
291 }
292}
293
294#[derive(Clone, Debug, Eq, PartialEq)]
296pub struct Storage {
297 pub pallet: String,
299 pub name: String,
301 pub docs: String,
303 pub type_id: u32,
305 pub key_id: Option<u32>,
307 pub query_all: bool,
309}
310
311impl Display for Storage {
312 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
313 write!(f, "{}", self.name)
314 }
315}
316
317impl Storage {
318 pub async fn query_all(
328 &self,
329 client: &OnlineClient<SubstrateConfig>,
330 keys: Vec<Value>,
331 ) -> Result<Vec<(Vec<Value>, RawValue)>, Error> {
332 let mut elements = Vec::new();
333 let metadata = client.metadata();
334 let types = metadata.types();
335 let storage_address = subxt::dynamic::storage(&self.pallet, &self.name, keys);
336 let mut stream = client
337 .storage()
338 .at_latest()
339 .await
340 .map_err(|e| Error::MetadataParsingError(format!("Failed to get storage: {}", e)))?
341 .iter(storage_address)
342 .await
343 .map_err(|e| {
344 Error::MetadataParsingError(format!("Failed to fetch storage value: {}", e))
345 })?;
346
347 while let Some(storage_data) = stream.try_next().await.map_err(|e| {
348 Error::MetadataParsingError(format!("Failed to fetch storage value: {}", e))
349 })? {
350 let keys = storage_data.keys;
351 let mut bytes = storage_data.value.encoded();
352 let decoded_value = scale_value::scale::decode_as_type(&mut bytes, self.type_id, types)
353 .map_err(|e| {
354 Error::MetadataParsingError(format!("Failed to decode storage value: {}", e))
355 })?;
356 elements.push((keys, decoded_value));
357 }
358 Ok(elements)
359 }
360 pub async fn query(
366 &self,
367 client: &OnlineClient<SubstrateConfig>,
368 keys: Vec<Value>,
369 ) -> Result<Option<RawValue>, Error> {
370 let metadata = client.metadata();
371 let types = metadata.types();
372 let storage_address = subxt::dynamic::storage(&self.pallet, &self.name, keys);
373 let storage_data = client
374 .storage()
375 .at_latest()
376 .await
377 .map_err(|e| Error::MetadataParsingError(format!("Failed to get storage: {}", e)))?
378 .fetch(&storage_address)
379 .await
380 .map_err(|e| {
381 Error::MetadataParsingError(format!("Failed to fetch storage value: {}", e))
382 })?;
383
384 match storage_data {
386 Some(value) => {
387 let mut bytes = value.encoded();
389 let decoded_value = scale_value::scale::decode_as_type(
390 &mut bytes,
391 self.type_id,
392 types,
393 )
394 .map_err(|e| {
395 Error::MetadataParsingError(format!("Failed to decode storage value: {}", e))
396 })?;
397
398 Ok(Some(decoded_value))
399 },
400 None => Ok(None),
401 }
402 }
403}
404
405fn extract_chain_state_from_pallet_metadata(
406 pallet: &PalletMetadata,
407) -> anyhow::Result<Vec<Storage>> {
408 pallet
409 .storage()
410 .map(|storage_metadata| {
411 storage_metadata
412 .entries()
413 .iter()
414 .map(|entry| {
415 Ok(Storage {
416 pallet: pallet.name().to_string(),
417 name: entry.name().to_string(),
418 docs: entry
419 .docs()
420 .iter()
421 .filter(|l| !l.is_empty())
422 .cloned()
423 .collect::<Vec<_>>()
424 .join("")
425 .trim()
426 .to_string(),
427 type_id: entry.entry_type().value_ty(),
428 key_id: match entry.entry_type() {
429 StorageEntryType::Plain(_) => None,
430 StorageEntryType::Map { key_ty, .. } => Some(*key_ty),
431 },
432 query_all: false,
433 })
434 })
435 .collect::<Result<Vec<Storage>, Error>>()
436 })
437 .unwrap_or_else(|| Ok(vec![]))
438 .map_err(|e| anyhow::Error::msg(e.to_string()))
439}
440
441fn extract_constants_from_pallet_metadata(
442 pallet: &PalletMetadata,
443 metadata: &Metadata,
444) -> anyhow::Result<Vec<Constant>> {
445 let types = metadata.types();
446 pallet
447 .constants()
448 .map(|constant| {
449 let mut value_bytes = constant.value();
451 let decoded_value =
452 scale_value::scale::decode_as_type(&mut value_bytes, constant.ty(), types)
453 .map_err(|e| {
454 Error::MetadataParsingError(format!(
455 "Failed to decode constant {}: {}",
456 constant.name(),
457 e
458 ))
459 })?;
460
461 Ok(Constant {
462 pallet: pallet.name().to_string(),
463 name: constant.name().to_string(),
464 docs: constant
465 .docs()
466 .iter()
467 .filter(|l| !l.is_empty())
468 .cloned()
469 .collect::<Vec<_>>()
470 .join("")
471 .trim()
472 .to_string(),
473 value: decoded_value,
474 })
475 })
476 .collect::<Result<Vec<Constant>, Error>>()
477 .map_err(|e| anyhow::Error::msg(e.to_string()))
478}
479
480fn extract_functions_from_pallet_metadata(
481 pallet: &PalletMetadata,
482 metadata: &Metadata,
483) -> anyhow::Result<Vec<Function>> {
484 pallet
485 .call_variants()
486 .map(|variants| {
487 variants
488 .iter()
489 .map(|variant| {
490 let mut is_supported = true;
491
492 let params = {
494 let mut parsed_params = Vec::new();
495 for field in &variant.fields {
496 match params::field_to_param(metadata, field) {
497 Ok(param) => parsed_params.push(param),
498 Err(_) => {
499 is_supported = false;
503 break;
504 },
505 }
506 }
507 parsed_params
508 };
509
510 Ok(Function {
511 pallet: pallet.name().to_string(),
512 name: variant.name.clone(),
513 index: variant.index,
514 docs: if is_supported {
515 variant
517 .docs
518 .iter()
519 .filter(|l| !l.is_empty())
520 .cloned()
521 .collect::<Vec<_>>()
522 .join(" ")
523 .trim()
524 .to_string()
525 } else {
526 "Function Not Supported".to_string()
528 },
529 params,
530 is_supported,
531 })
532 })
533 .collect::<Result<Vec<Function>, Error>>()
534 })
535 .unwrap_or_else(|| Ok(vec![]))
536 .map_err(|e| anyhow::Error::msg(e.to_string()))
537}
538
539pub fn parse_chain_metadata(client: &OnlineClient<SubstrateConfig>) -> Result<Vec<Pallet>, Error> {
546 let metadata: Metadata = client.metadata();
547
548 let pallets = metadata
549 .pallets()
550 .map(|pallet| {
551 Ok(Pallet {
552 name: pallet.name().to_string(),
553 index: pallet.index(),
554 docs: pallet.docs().join("").trim().to_string(),
555 functions: extract_functions_from_pallet_metadata(&pallet, &metadata)?,
556 constants: extract_constants_from_pallet_metadata(&pallet, &metadata)?,
557 state: extract_chain_state_from_pallet_metadata(&pallet)?,
558 })
559 })
560 .collect::<Result<Vec<Pallet>, Error>>()?;
561
562 Ok(pallets)
563}
564
565pub fn find_pallet_by_name<'a>(
571 pallets: &'a [Pallet],
572 pallet_name: &str,
573) -> Result<&'a Pallet, Error> {
574 if let Some(pallet) = pallets.iter().find(|p| p.name == pallet_name) {
575 Ok(pallet)
576 } else {
577 Err(Error::PalletNotFound(pallet_name.to_string()))
578 }
579}
580
581pub fn find_callable_by_name(
588 pallets: &[Pallet],
589 pallet_name: &str,
590 function_name: &str,
591) -> Result<CallItem, Error> {
592 let pallet = find_pallet_by_name(pallets, pallet_name)?;
593 if let Some(function) = pallet.functions.iter().find(|&e| e.name == function_name) {
594 return Ok(CallItem::Function(function.clone()))
595 }
596 if let Some(constant) = pallet.constants.iter().find(|&e| e.name == function_name) {
597 return Ok(CallItem::Constant(constant.clone()))
598 }
599 if let Some(storage) = pallet.state.iter().find(|&e| e.name == function_name) {
600 return Ok(CallItem::Storage(storage.clone()))
601 }
602 Err(Error::FunctionNotFound(format!(
603 "Could not find a function, constant or storage with the name \"{function_name}\""
604 )))
605}
606
607pub fn parse_dispatchable_arguments(
615 params: &[Param],
616 raw_params: Vec<String>,
617) -> Result<Vec<Value>, Error> {
618 params
619 .iter()
620 .zip(raw_params)
621 .map(|(param, raw_param)| {
622 let processed_param = if param.is_sequence && !raw_param.starts_with("0x") {
624 to_hex(&raw_param)
625 } else {
626 raw_param
627 };
628 scale_value::stringify::from_str_custom()
629 .add_custom_parser(custom_parsers::parse_hex)
630 .add_custom_parser(custom_parsers::parse_ss58)
631 .parse(&processed_param)
632 .0
633 .map_err(|_| Error::ParamProcessingError)
634 })
635 .collect()
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use anyhow::Result;
642 use sp_core::bytes::from_hex;
643 use subxt::ext::scale_bits;
644
645 #[test]
646 fn parse_dispatchable_arguments_works() -> Result<()> {
647 let args = [
650 "1".to_string(),
651 "-1".to_string(),
652 "true".to_string(),
653 "'a'".to_string(),
654 "\"hi\"".to_string(),
655 "{ a: true, b: \"hello\" }".to_string(),
656 "MyVariant { a: true, b: \"hello\" }".to_string(),
657 "<0101>".to_string(),
658 "(1,2,0x030405)".to_string(),
659 r#"{
660 name: "Alice",
661 address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty
662 }"#
663 .to_string(),
664 ]
665 .to_vec();
666 let addr: Vec<_> =
667 from_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?
668 .into_iter()
669 .map(|b| Value::u128(b as u128))
670 .collect();
671 let params = vec![
673 Param { type_name: "u128".to_string(), ..Default::default() },
674 Param { type_name: "i128".to_string(), ..Default::default() },
675 Param { type_name: "bool".to_string(), ..Default::default() },
676 Param { type_name: "char".to_string(), ..Default::default() },
677 Param { type_name: "string".to_string(), ..Default::default() },
678 Param { type_name: "composite".to_string(), ..Default::default() },
679 Param { type_name: "variant".to_string(), is_variant: true, ..Default::default() },
680 Param { type_name: "bit_sequence".to_string(), ..Default::default() },
681 Param { type_name: "tuple".to_string(), is_tuple: true, ..Default::default() },
682 Param { type_name: "composite".to_string(), ..Default::default() },
683 ];
684 assert_eq!(
685 parse_dispatchable_arguments(¶ms, args)?,
686 [
687 Value::u128(1),
688 Value::i128(-1),
689 Value::bool(true),
690 Value::char('a'),
691 Value::string("hi"),
692 Value::named_composite(vec![
693 ("a", Value::bool(true)),
694 ("b", Value::string("hello"))
695 ]),
696 Value::named_variant(
697 "MyVariant",
698 vec![("a", Value::bool(true)), ("b", Value::string("hello"))]
699 ),
700 Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])),
701 Value::unnamed_composite(vec![
702 Value::u128(1),
703 Value::u128(2),
704 Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),])
705 ]),
706 Value::named_composite(vec![
707 ("name", Value::string("Alice")),
708 ("address", Value::unnamed_composite(addr))
709 ])
710 ]
711 );
712 Ok(())
713 }
714
715 #[test]
716 fn constant_display_works() {
717 let value = Value::u128(250).map_context(|_| 0u32);
718 let constant = Constant {
719 pallet: "System".to_string(),
720 name: "BlockHashCount".to_string(),
721 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
722 value,
723 };
724 assert_eq!(format!("{constant}"), "BlockHashCount");
725 }
726
727 #[test]
728 fn constant_struct_fields_work() {
729 let value = Value::u128(100).map_context(|_| 0u32);
730 let constant = Constant {
731 pallet: "Balances".to_string(),
732 name: "ExistentialDeposit".to_string(),
733 docs: "The minimum amount required to keep an account open.".to_string(),
734 value: value.clone(),
735 };
736 assert_eq!(constant.pallet, "Balances");
737 assert_eq!(constant.name, "ExistentialDeposit");
738 assert_eq!(constant.docs, "The minimum amount required to keep an account open.");
739 assert_eq!(constant.value, value);
740 }
741
742 #[test]
743 fn storage_display_works() {
744 let storage = Storage {
745 pallet: "System".to_string(),
746 name: "Account".to_string(),
747 docs: "The full account information for a particular account ID.".to_string(),
748 type_id: 42,
749 key_id: None,
750 query_all: false,
751 };
752 assert_eq!(format!("{storage}"), "Account");
753 }
754
755 #[test]
756 fn pallet_with_constants_and_storage() {
757 let value = Value::u128(250).map_context(|_| 0u32);
759 let pallet = Pallet {
760 name: "System".to_string(),
761 index: 0,
762 docs: "System pallet".to_string(),
763 functions: vec![],
764 constants: vec![Constant {
765 pallet: "System".to_string(),
766 name: "BlockHashCount".to_string(),
767 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
768 value,
769 }],
770 state: vec![Storage {
771 pallet: "System".to_string(),
772 name: "Account".to_string(),
773 docs: "The full account information for a particular account ID.".to_string(),
774 type_id: 42,
775 key_id: None,
776 query_all: false,
777 }],
778 };
779 assert_eq!(pallet.constants.len(), 1);
780 assert_eq!(pallet.state.len(), 1);
781 assert_eq!(pallet.constants[0].name, "BlockHashCount");
782 assert_eq!(pallet.state[0].name, "Account");
783 }
784
785 #[test]
786 fn storage_struct_with_key_id_works() {
787 let plain_storage = Storage {
789 pallet: "Timestamp".to_string(),
790 name: "Now".to_string(),
791 docs: "Current time for the current block.".to_string(),
792 type_id: 10,
793 key_id: None,
794 query_all: false,
795 };
796 assert_eq!(plain_storage.pallet, "Timestamp");
797 assert_eq!(plain_storage.name, "Now");
798 assert!(plain_storage.key_id.is_none());
799
800 let map_storage = Storage {
802 pallet: "System".to_string(),
803 name: "Account".to_string(),
804 docs: "The full account information for a particular account ID.".to_string(),
805 type_id: 42,
806 key_id: Some(100),
807 query_all: false,
808 };
809 assert_eq!(map_storage.pallet, "System");
810 assert_eq!(map_storage.name, "Account");
811 assert_eq!(map_storage.key_id, Some(100));
812 }
813
814 #[test]
815 fn raw_value_to_string_works() -> Result<()> {
816 let value = Value::u128(250).map_context(|_| 0u32);
818 let result = raw_value_to_string(&value, "")?;
819 assert_eq!(result, "250");
820
821 let value = Value::bool(true).map_context(|_| 0u32);
823 let result = raw_value_to_string(&value, "")?;
824 assert_eq!(result, "true");
825
826 let value = Value::string("hello").map_context(|_| 0u32);
828 let result = raw_value_to_string(&value, "")?;
829 assert_eq!(result, "\"hello\"");
830
831 let inner = Value::u128(42);
833 let value = Value::unnamed_composite(vec![inner]).map_context(|_| 0u32);
834 let result = raw_value_to_string(&value, "")?;
835 assert_eq!(result, "0x2a"); let value =
839 Value::unnamed_composite(vec![Value::u128(1), Value::u128(2)]).map_context(|_| 0u32);
840 let result = raw_value_to_string(&value, "")?;
841 assert_eq!(result, "0x0102"); Ok(())
844 }
845
846 #[test]
847 fn call_item_default_works() {
848 let item = CallItem::default();
849 assert!(matches!(item, CallItem::Function(_)));
850 if let CallItem::Function(f) = item {
851 assert_eq!(f, Function::default());
852 }
853 }
854
855 #[test]
856 fn call_item_display_works() {
857 let function = Function {
858 pallet: "System".to_string(),
859 name: "remark".to_string(),
860 ..Default::default()
861 };
862 let item = CallItem::Function(function);
863 assert_eq!(format!("{item}"), "remark");
864
865 let constant = Constant {
866 pallet: "System".to_string(),
867 name: "BlockHashCount".to_string(),
868 docs: "docs".to_string(),
869 value: Value::u128(250).map_context(|_| 0u32),
870 };
871 let item = CallItem::Constant(constant);
872 assert_eq!(format!("{item}"), "BlockHashCount");
873
874 let storage = Storage {
875 pallet: "System".to_string(),
876 name: "Account".to_string(),
877 docs: "docs".to_string(),
878 type_id: 42,
879 key_id: None,
880 query_all: false,
881 };
882 let item = CallItem::Storage(storage);
883 assert_eq!(format!("{item}"), "Account");
884 }
885
886 #[test]
887 fn call_item_as_methods_work() {
888 let function = Function {
889 pallet: "System".to_string(),
890 name: "remark".to_string(),
891 ..Default::default()
892 };
893 let item = CallItem::Function(function.clone());
894 assert_eq!(item.as_function(), Some(&function));
895 assert_eq!(item.as_constant(), None);
896 assert_eq!(item.as_storage(), None);
897
898 let constant = Constant {
899 pallet: "System".to_string(),
900 name: "BlockHashCount".to_string(),
901 docs: "docs".to_string(),
902 value: Value::u128(250).map_context(|_| 0u32),
903 };
904 let item = CallItem::Constant(constant.clone());
905 assert_eq!(item.as_function(), None);
906 assert_eq!(item.as_constant(), Some(&constant));
907 assert_eq!(item.as_storage(), None);
908
909 let storage = Storage {
910 pallet: "System".to_string(),
911 name: "Account".to_string(),
912 docs: "docs".to_string(),
913 type_id: 42,
914 key_id: None,
915 query_all: false,
916 };
917 let item = CallItem::Storage(storage.clone());
918 assert_eq!(item.as_function(), None);
919 assert_eq!(item.as_constant(), None);
920 assert_eq!(item.as_storage(), Some(&storage));
921 }
922
923 #[test]
924 fn call_item_name_works() {
925 let function = Function {
926 pallet: "System".to_string(),
927 name: "remark".to_string(),
928 ..Default::default()
929 };
930 let item = CallItem::Function(function);
931 assert_eq!(item.name(), "remark");
932
933 let constant = Constant {
934 pallet: "System".to_string(),
935 name: "BlockHashCount".to_string(),
936 docs: "docs".to_string(),
937 value: Value::u128(250).map_context(|_| 0u32),
938 };
939 let item = CallItem::Constant(constant);
940 assert_eq!(item.name(), "BlockHashCount");
941
942 let storage = Storage {
943 pallet: "System".to_string(),
944 name: "Account".to_string(),
945 docs: "docs".to_string(),
946 type_id: 42,
947 key_id: None,
948 query_all: false,
949 };
950 let item = CallItem::Storage(storage);
951 assert_eq!(item.name(), "Account");
952 }
953
954 #[test]
955 fn call_item_hint_works() {
956 let function = Function {
957 pallet: "System".to_string(),
958 name: "remark".to_string(),
959 ..Default::default()
960 };
961 let item = CallItem::Function(function);
962 assert_eq!(item.hint(), "📝 [EXTRINSIC]");
963
964 let constant = Constant {
965 pallet: "System".to_string(),
966 name: "BlockHashCount".to_string(),
967 docs: "docs".to_string(),
968 value: Value::u128(250).map_context(|_| 0u32),
969 };
970 let item = CallItem::Constant(constant);
971 assert_eq!(item.hint(), "[CONSTANT]");
972
973 let storage = Storage {
974 pallet: "System".to_string(),
975 name: "Account".to_string(),
976 docs: "docs".to_string(),
977 type_id: 42,
978 key_id: None,
979 query_all: false,
980 };
981 let item = CallItem::Storage(storage);
982 assert_eq!(item.hint(), "[STORAGE]");
983 }
984
985 #[test]
986 fn call_item_docs_works() {
987 let function = Function {
988 pallet: "System".to_string(),
989 name: "remark".to_string(),
990 docs: "Make some on-chain remark.".to_string(),
991 ..Default::default()
992 };
993 let item = CallItem::Function(function);
994 assert_eq!(item.docs(), "Make some on-chain remark.");
995
996 let constant = Constant {
997 pallet: "System".to_string(),
998 name: "BlockHashCount".to_string(),
999 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
1000 value: Value::u128(250).map_context(|_| 0u32),
1001 };
1002 let item = CallItem::Constant(constant);
1003 assert_eq!(item.docs(), "Maximum number of block number to block hash mappings to keep.");
1004
1005 let storage = Storage {
1006 pallet: "System".to_string(),
1007 name: "Account".to_string(),
1008 docs: "The full account information for a particular account ID.".to_string(),
1009 type_id: 42,
1010 key_id: None,
1011 query_all: false,
1012 };
1013 let item = CallItem::Storage(storage);
1014 assert_eq!(item.docs(), "The full account information for a particular account ID.");
1015 }
1016
1017 #[test]
1018 fn call_item_pallet_works() {
1019 let function = Function {
1020 pallet: "System".to_string(),
1021 name: "remark".to_string(),
1022 ..Default::default()
1023 };
1024 let item = CallItem::Function(function);
1025 assert_eq!(item.pallet(), "System");
1026
1027 let constant = Constant {
1028 pallet: "Balances".to_string(),
1029 name: "ExistentialDeposit".to_string(),
1030 docs: "docs".to_string(),
1031 value: Value::u128(100).map_context(|_| 0u32),
1032 };
1033 let item = CallItem::Constant(constant);
1034 assert_eq!(item.pallet(), "Balances");
1035
1036 let storage = Storage {
1037 pallet: "Timestamp".to_string(),
1038 name: "Now".to_string(),
1039 docs: "docs".to_string(),
1040 type_id: 10,
1041 key_id: None,
1042 query_all: false,
1043 };
1044 let item = CallItem::Storage(storage);
1045 assert_eq!(item.pallet(), "Timestamp");
1046 }
1047
1048 #[test]
1049 fn pallet_get_all_callables_works() {
1050 let function = Function {
1051 pallet: "System".to_string(),
1052 name: "remark".to_string(),
1053 ..Default::default()
1054 };
1055 let constant = Constant {
1056 pallet: "System".to_string(),
1057 name: "BlockHashCount".to_string(),
1058 docs: "docs".to_string(),
1059 value: Value::u128(250).map_context(|_| 0u32),
1060 };
1061 let storage = Storage {
1062 pallet: "System".to_string(),
1063 name: "Account".to_string(),
1064 docs: "docs".to_string(),
1065 type_id: 42,
1066 key_id: None,
1067 query_all: false,
1068 };
1069
1070 let pallet = Pallet {
1071 name: "System".to_string(),
1072 index: 0,
1073 docs: "System pallet".to_string(),
1074 functions: vec![function.clone()],
1075 constants: vec![constant.clone()],
1076 state: vec![storage.clone()],
1077 };
1078
1079 let callables = pallet.get_all_callables();
1080 assert_eq!(callables.len(), 3);
1081 assert!(matches!(callables[0], CallItem::Function(_)));
1082 assert!(matches!(callables[1], CallItem::Constant(_)));
1083 assert!(matches!(callables[2], CallItem::Storage(_)));
1084
1085 if let CallItem::Function(f) = &callables[0] {
1087 assert_eq!(f, &function);
1088 }
1089 if let CallItem::Constant(c) = &callables[1] {
1090 assert_eq!(c, &constant);
1091 }
1092 if let CallItem::Storage(s) = &callables[2] {
1093 assert_eq!(s, &storage);
1094 }
1095 }
1096
1097 #[test]
1098 fn find_callable_by_name_works() {
1099 let function = Function {
1100 pallet: "System".to_string(),
1101 name: "remark".to_string(),
1102 ..Default::default()
1103 };
1104 let constant = Constant {
1105 pallet: "System".to_string(),
1106 name: "BlockHashCount".to_string(),
1107 docs: "docs".to_string(),
1108 value: Value::u128(250).map_context(|_| 0u32),
1109 };
1110 let storage = Storage {
1111 pallet: "System".to_string(),
1112 name: "Account".to_string(),
1113 docs: "docs".to_string(),
1114 type_id: 42,
1115 key_id: None,
1116 query_all: false,
1117 };
1118
1119 let pallets = vec![Pallet {
1120 name: "System".to_string(),
1121 index: 0,
1122 docs: "System pallet".to_string(),
1123 functions: vec![function.clone()],
1124 constants: vec![constant.clone()],
1125 state: vec![storage.clone()],
1126 }];
1127
1128 let result = find_callable_by_name(&pallets, "System", "remark");
1130 assert!(result.is_ok());
1131 if let Ok(CallItem::Function(f)) = result {
1132 assert_eq!(f.name, "remark");
1133 }
1134
1135 let result = find_callable_by_name(&pallets, "System", "BlockHashCount");
1137 assert!(result.is_ok());
1138 if let Ok(CallItem::Constant(c)) = result {
1139 assert_eq!(c.name, "BlockHashCount");
1140 }
1141
1142 let result = find_callable_by_name(&pallets, "System", "Account");
1144 assert!(result.is_ok());
1145 if let Ok(CallItem::Storage(s)) = result {
1146 assert_eq!(s.name, "Account");
1147 }
1148
1149 let result = find_callable_by_name(&pallets, "System", "NonExistent");
1151 assert!(result.is_err());
1152 assert!(matches!(result.unwrap_err(), Error::FunctionNotFound(_)));
1153
1154 let result = find_callable_by_name(&pallets, "NonExistent", "remark");
1156 assert!(result.is_err());
1157 assert!(matches!(result.unwrap_err(), Error::PalletNotFound(_)));
1158 }
1159
1160 #[test]
1161 fn format_single_tuples_single_element_works() -> Result<()> {
1162 let inner_value = Value::u128(42);
1164 let single_tuple = Value::unnamed_composite(vec![inner_value]).map_context(|_| 0u32);
1165
1166 let mut output = String::new();
1167 let result = format_single_tuples(&single_tuple, &mut output);
1168
1169 assert!(result.is_some());
1171 assert!(result.unwrap().is_ok());
1172 assert_eq!(output, "42");
1173 Ok(())
1174 }
1175
1176 #[test]
1177 fn format_single_tuples_multi_element_returns_none() -> Result<()> {
1178 let tuple =
1180 Value::unnamed_composite(vec![Value::u128(1), Value::u128(2)]).map_context(|_| 0u32);
1181
1182 let mut output = String::new();
1183 let result = format_single_tuples(&tuple, &mut output);
1184
1185 assert!(result.is_none());
1187 assert_eq!(output, "");
1188 Ok(())
1189 }
1190
1191 #[test]
1192 fn format_single_tuples_empty_tuple_returns_none() -> Result<()> {
1193 let empty_tuple = Value::unnamed_composite(vec![]).map_context(|_| 0u32);
1195
1196 let mut output = String::new();
1197 let result = format_single_tuples(&empty_tuple, &mut output);
1198
1199 assert!(result.is_none());
1201 assert_eq!(output, "");
1202 Ok(())
1203 }
1204
1205 #[test]
1206 fn format_single_tuples_non_composite_returns_none() -> Result<()> {
1207 let simple_value = Value::u128(42).map_context(|_| 0u32);
1209
1210 let mut output = String::new();
1211 let result = format_single_tuples(&simple_value, &mut output);
1212
1213 assert!(result.is_none());
1215 assert_eq!(output, "");
1216 Ok(())
1217 }
1218
1219 #[test]
1220 fn format_single_tuples_named_composite_returns_none() -> Result<()> {
1221 let named_composite =
1223 Value::named_composite(vec![("field", Value::u128(42))]).map_context(|_| 0u32);
1224
1225 let mut output = String::new();
1226 let result = format_single_tuples(&named_composite, &mut output);
1227
1228 assert!(result.is_none());
1230 assert_eq!(output, "");
1231 Ok(())
1232 }
1233
1234 #[tokio::test]
1235 async fn query_storage_works() -> Result<()> {
1236 use crate::{parse_chain_metadata, set_up_client};
1237 use pop_common::test_env::TestNode;
1238
1239 let node = TestNode::spawn().await?;
1241 let client = set_up_client(node.ws_url()).await?;
1242 let pallets = parse_chain_metadata(&client)?;
1243
1244 let storage = pallets
1246 .iter()
1247 .find(|p| p.name == "System")
1248 .and_then(|p| p.state.iter().find(|s| s.name == "Number"))
1249 .expect("System::Number storage should exist");
1250
1251 let result = storage.query(&client, vec![]).await?;
1253
1254 assert!(result.is_some());
1256 let value = result.unwrap();
1257 assert!(matches!(value.value, ValueDef::Primitive(_)));
1259 Ok(())
1260 }
1261
1262 #[tokio::test]
1263 async fn query_storage_with_key_works() -> Result<()> {
1264 use crate::{parse_chain_metadata, set_up_client};
1265 use pop_common::test_env::TestNode;
1266
1267 let node = TestNode::spawn().await?;
1269 let client = set_up_client(node.ws_url()).await?;
1270 let pallets = parse_chain_metadata(&client)?;
1271
1272 let storage = pallets
1274 .iter()
1275 .find(|p| p.name == "System")
1276 .and_then(|p| p.state.iter().find(|s| s.name == "Account"))
1277 .expect("System::Account storage should exist");
1278
1279 let alice_address = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
1281 let account_key = scale_value::stringify::from_str_custom()
1282 .add_custom_parser(custom_parsers::parse_ss58)
1283 .parse(alice_address)
1284 .0
1285 .expect("Should parse Alice's address");
1286
1287 let result = storage.query(&client, vec![account_key]).await?;
1289
1290 assert!(result.is_some());
1292 Ok(())
1293 }
1294
1295 #[test]
1296 fn render_storage_key_values_with_keys_works() -> Result<()> {
1297 let key1 = Value::u128(42);
1299 let key2 = Value::string("test_key");
1300 let value = Value::bool(true).map_context(|_| 0u32);
1301
1302 let key_value_pairs = vec![(vec![key1, key2], value)];
1303
1304 let result = render_storage_key_values(&key_value_pairs)?;
1305
1306 let expected = "[\n 42,\n \"test_key\",\n true\n]\n";
1308 assert_eq!(result, expected);
1309 Ok(())
1310 }
1311
1312 #[test]
1313 fn render_storage_key_values_without_keys_works() -> Result<()> {
1314 let value = Value::u128(100).map_context(|_| 0u32);
1316
1317 let key_value_pairs = vec![(vec![], value)];
1318
1319 let result = render_storage_key_values(&key_value_pairs)?;
1320
1321 let expected = "[\n 100\n]\n";
1323 assert_eq!(result, expected);
1324 Ok(())
1325 }
1326}