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") {
623 if param.type_name == "[u8]" {
624 to_hex(&raw_param)
626 } else {
627 convert_brackets_to_parens(&raw_param)
631 }
632 } else {
633 raw_param
634 };
635 scale_value::stringify::from_str_custom()
636 .add_custom_parser(custom_parsers::parse_hex)
637 .add_custom_parser(custom_parsers::parse_ss58)
638 .parse(&processed_param)
639 .0
640 .map_err(|_| Error::ParamProcessingError)
641 })
642 .collect()
643}
644
645fn convert_brackets_to_parens(input: &str) -> String {
648 let trimmed = input.trim();
649 if trimmed.starts_with('[') && trimmed.ends_with(']') {
650 format!("({})", &trimmed[1..trimmed.len() - 1])
651 } else {
652 input.to_string()
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use anyhow::Result;
660 use sp_core::bytes::from_hex;
661 use subxt::ext::scale_bits;
662
663 #[test]
664 fn parse_dispatchable_arguments_works() -> Result<()> {
665 let args = [
668 "1".to_string(),
669 "-1".to_string(),
670 "true".to_string(),
671 "'a'".to_string(),
672 "\"hi\"".to_string(),
673 "{ a: true, b: \"hello\" }".to_string(),
674 "MyVariant { a: true, b: \"hello\" }".to_string(),
675 "<0101>".to_string(),
676 "(1,2,0x030405)".to_string(),
677 r#"{
678 name: "Alice",
679 address: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty
680 }"#
681 .to_string(),
682 ]
683 .to_vec();
684 let addr: Vec<_> =
685 from_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?
686 .into_iter()
687 .map(|b| Value::u128(b as u128))
688 .collect();
689 let params = vec![
691 Param { type_name: "u128".to_string(), ..Default::default() },
692 Param { type_name: "i128".to_string(), ..Default::default() },
693 Param { type_name: "bool".to_string(), ..Default::default() },
694 Param { type_name: "char".to_string(), ..Default::default() },
695 Param { type_name: "string".to_string(), ..Default::default() },
696 Param { type_name: "composite".to_string(), ..Default::default() },
697 Param { type_name: "variant".to_string(), is_variant: true, ..Default::default() },
698 Param { type_name: "bit_sequence".to_string(), ..Default::default() },
699 Param { type_name: "tuple".to_string(), is_tuple: true, ..Default::default() },
700 Param { type_name: "composite".to_string(), ..Default::default() },
701 ];
702 assert_eq!(
703 parse_dispatchable_arguments(¶ms, args)?,
704 [
705 Value::u128(1),
706 Value::i128(-1),
707 Value::bool(true),
708 Value::char('a'),
709 Value::string("hi"),
710 Value::named_composite(vec![
711 ("a", Value::bool(true)),
712 ("b", Value::string("hello"))
713 ]),
714 Value::named_variant(
715 "MyVariant",
716 vec![("a", Value::bool(true)), ("b", Value::string("hello"))]
717 ),
718 Value::bit_sequence(scale_bits::Bits::from_iter([false, true, false, true])),
719 Value::unnamed_composite(vec![
720 Value::u128(1),
721 Value::u128(2),
722 Value::unnamed_composite(vec![Value::u128(3), Value::u128(4), Value::u128(5),])
723 ]),
724 Value::named_composite(vec![
725 ("name", Value::string("Alice")),
726 ("address", Value::unnamed_composite(addr))
727 ])
728 ]
729 );
730 Ok(())
731 }
732
733 #[test]
734 fn parse_vec_account_id() -> Result<()> {
735 let params = vec![Param {
737 name: "who".into(),
738 type_name: "[AccountId32 ([u8;32])]".into(),
739 is_sequence: true,
740 ..Default::default()
741 }];
742 let args = vec!["[5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty]".into()];
743 let result = parse_dispatchable_arguments(¶ms, args);
744 assert!(result.is_ok(), "Failed to parse: {:?}", result);
745
746 let values = result?;
748 assert_eq!(values.len(), 1);
749
750 let addr: Vec<_> =
752 from_hex("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?
753 .into_iter()
754 .map(|b| Value::u128(b as u128))
755 .collect();
756
757 assert_eq!(values[0], Value::unnamed_composite(vec![Value::unnamed_composite(addr)]));
758 Ok(())
759 }
760
761 #[test]
762 fn parse_vec_multiple_account_ids() -> Result<()> {
763 let params = vec![Param {
765 name: "who".into(),
766 type_name: "[AccountId32 ([u8;32])]".into(),
767 is_sequence: true,
768 ..Default::default()
769 }];
770 let args = vec![
771 "[5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty, 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY]".into(),
772 ];
773 let result = parse_dispatchable_arguments(¶ms, args);
774 assert!(result.is_ok());
775
776 let values = result?;
777 assert_eq!(values.len(), 1);
778
779 if let ValueDef::Composite(composite) = &values[0].value {
781 match composite {
782 Composite::Unnamed(items) => assert_eq!(items.len(), 2),
783 _ => panic!("Expected unnamed composite"),
784 }
785 } else {
786 panic!("Expected composite value");
787 }
788 Ok(())
789 }
790
791 #[test]
792 fn parse_byte_sequence_still_works() -> Result<()> {
793 let params = vec![Param {
795 name: "remark".into(),
796 type_name: "[u8]".into(),
797 is_sequence: true,
798 ..Default::default()
799 }];
800 let args = vec!["hello".into()];
801 let result = parse_dispatchable_arguments(¶ms, args);
802 assert!(result.is_ok());
803 Ok(())
804 }
805
806 #[test]
807 fn convert_brackets_to_parens_works() {
808 assert_eq!(convert_brackets_to_parens("[a, b, c]"), "(a, b, c)");
810 assert_eq!(convert_brackets_to_parens("[x]"), "(x)");
812 assert_eq!(convert_brackets_to_parens(" [a, b] "), "(a, b)");
814 assert_eq!(convert_brackets_to_parens("[]"), "()");
816 assert_eq!(convert_brackets_to_parens("hello"), "hello");
818 assert_eq!(convert_brackets_to_parens("(a, b)"), "(a, b)");
819 assert_eq!(convert_brackets_to_parens("[[1, 2], [3, 4]]"), "([1, 2], [3, 4])");
821 }
822
823 #[test]
824 fn constant_display_works() {
825 let value = Value::u128(250).map_context(|_| 0u32);
826 let constant = Constant {
827 pallet: "System".to_string(),
828 name: "BlockHashCount".to_string(),
829 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
830 value,
831 };
832 assert_eq!(format!("{constant}"), "BlockHashCount");
833 }
834
835 #[test]
836 fn constant_struct_fields_work() {
837 let value = Value::u128(100).map_context(|_| 0u32);
838 let constant = Constant {
839 pallet: "Balances".to_string(),
840 name: "ExistentialDeposit".to_string(),
841 docs: "The minimum amount required to keep an account open.".to_string(),
842 value: value.clone(),
843 };
844 assert_eq!(constant.pallet, "Balances");
845 assert_eq!(constant.name, "ExistentialDeposit");
846 assert_eq!(constant.docs, "The minimum amount required to keep an account open.");
847 assert_eq!(constant.value, value);
848 }
849
850 #[test]
851 fn storage_display_works() {
852 let storage = Storage {
853 pallet: "System".to_string(),
854 name: "Account".to_string(),
855 docs: "The full account information for a particular account ID.".to_string(),
856 type_id: 42,
857 key_id: None,
858 query_all: false,
859 };
860 assert_eq!(format!("{storage}"), "Account");
861 }
862
863 #[test]
864 fn pallet_with_constants_and_storage() {
865 let value = Value::u128(250).map_context(|_| 0u32);
867 let pallet = Pallet {
868 name: "System".to_string(),
869 index: 0,
870 docs: "System pallet".to_string(),
871 functions: vec![],
872 constants: vec![Constant {
873 pallet: "System".to_string(),
874 name: "BlockHashCount".to_string(),
875 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
876 value,
877 }],
878 state: vec![Storage {
879 pallet: "System".to_string(),
880 name: "Account".to_string(),
881 docs: "The full account information for a particular account ID.".to_string(),
882 type_id: 42,
883 key_id: None,
884 query_all: false,
885 }],
886 };
887 assert_eq!(pallet.constants.len(), 1);
888 assert_eq!(pallet.state.len(), 1);
889 assert_eq!(pallet.constants[0].name, "BlockHashCount");
890 assert_eq!(pallet.state[0].name, "Account");
891 }
892
893 #[test]
894 fn storage_struct_with_key_id_works() {
895 let plain_storage = Storage {
897 pallet: "Timestamp".to_string(),
898 name: "Now".to_string(),
899 docs: "Current time for the current block.".to_string(),
900 type_id: 10,
901 key_id: None,
902 query_all: false,
903 };
904 assert_eq!(plain_storage.pallet, "Timestamp");
905 assert_eq!(plain_storage.name, "Now");
906 assert!(plain_storage.key_id.is_none());
907
908 let map_storage = Storage {
910 pallet: "System".to_string(),
911 name: "Account".to_string(),
912 docs: "The full account information for a particular account ID.".to_string(),
913 type_id: 42,
914 key_id: Some(100),
915 query_all: false,
916 };
917 assert_eq!(map_storage.pallet, "System");
918 assert_eq!(map_storage.name, "Account");
919 assert_eq!(map_storage.key_id, Some(100));
920 }
921
922 #[test]
923 fn raw_value_to_string_works() -> Result<()> {
924 let value = Value::u128(250).map_context(|_| 0u32);
926 let result = raw_value_to_string(&value, "")?;
927 assert_eq!(result, "250");
928
929 let value = Value::bool(true).map_context(|_| 0u32);
931 let result = raw_value_to_string(&value, "")?;
932 assert_eq!(result, "true");
933
934 let value = Value::string("hello").map_context(|_| 0u32);
936 let result = raw_value_to_string(&value, "")?;
937 assert_eq!(result, "\"hello\"");
938
939 let inner = Value::u128(42);
941 let value = Value::unnamed_composite(vec![inner]).map_context(|_| 0u32);
942 let result = raw_value_to_string(&value, "")?;
943 assert_eq!(result, "0x2a"); let value =
947 Value::unnamed_composite(vec![Value::u128(1), Value::u128(2)]).map_context(|_| 0u32);
948 let result = raw_value_to_string(&value, "")?;
949 assert_eq!(result, "0x0102"); Ok(())
952 }
953
954 #[test]
955 fn call_item_default_works() {
956 let item = CallItem::default();
957 assert!(matches!(item, CallItem::Function(_)));
958 if let CallItem::Function(f) = item {
959 assert_eq!(f, Function::default());
960 }
961 }
962
963 #[test]
964 fn call_item_display_works() {
965 let function = Function {
966 pallet: "System".to_string(),
967 name: "remark".to_string(),
968 ..Default::default()
969 };
970 let item = CallItem::Function(function);
971 assert_eq!(format!("{item}"), "remark");
972
973 let constant = Constant {
974 pallet: "System".to_string(),
975 name: "BlockHashCount".to_string(),
976 docs: "docs".to_string(),
977 value: Value::u128(250).map_context(|_| 0u32),
978 };
979 let item = CallItem::Constant(constant);
980 assert_eq!(format!("{item}"), "BlockHashCount");
981
982 let storage = Storage {
983 pallet: "System".to_string(),
984 name: "Account".to_string(),
985 docs: "docs".to_string(),
986 type_id: 42,
987 key_id: None,
988 query_all: false,
989 };
990 let item = CallItem::Storage(storage);
991 assert_eq!(format!("{item}"), "Account");
992 }
993
994 #[test]
995 fn call_item_as_methods_work() {
996 let function = Function {
997 pallet: "System".to_string(),
998 name: "remark".to_string(),
999 ..Default::default()
1000 };
1001 let item = CallItem::Function(function.clone());
1002 assert_eq!(item.as_function(), Some(&function));
1003 assert_eq!(item.as_constant(), None);
1004 assert_eq!(item.as_storage(), None);
1005
1006 let constant = Constant {
1007 pallet: "System".to_string(),
1008 name: "BlockHashCount".to_string(),
1009 docs: "docs".to_string(),
1010 value: Value::u128(250).map_context(|_| 0u32),
1011 };
1012 let item = CallItem::Constant(constant.clone());
1013 assert_eq!(item.as_function(), None);
1014 assert_eq!(item.as_constant(), Some(&constant));
1015 assert_eq!(item.as_storage(), None);
1016
1017 let storage = Storage {
1018 pallet: "System".to_string(),
1019 name: "Account".to_string(),
1020 docs: "docs".to_string(),
1021 type_id: 42,
1022 key_id: None,
1023 query_all: false,
1024 };
1025 let item = CallItem::Storage(storage.clone());
1026 assert_eq!(item.as_function(), None);
1027 assert_eq!(item.as_constant(), None);
1028 assert_eq!(item.as_storage(), Some(&storage));
1029 }
1030
1031 #[test]
1032 fn call_item_name_works() {
1033 let function = Function {
1034 pallet: "System".to_string(),
1035 name: "remark".to_string(),
1036 ..Default::default()
1037 };
1038 let item = CallItem::Function(function);
1039 assert_eq!(item.name(), "remark");
1040
1041 let constant = Constant {
1042 pallet: "System".to_string(),
1043 name: "BlockHashCount".to_string(),
1044 docs: "docs".to_string(),
1045 value: Value::u128(250).map_context(|_| 0u32),
1046 };
1047 let item = CallItem::Constant(constant);
1048 assert_eq!(item.name(), "BlockHashCount");
1049
1050 let storage = Storage {
1051 pallet: "System".to_string(),
1052 name: "Account".to_string(),
1053 docs: "docs".to_string(),
1054 type_id: 42,
1055 key_id: None,
1056 query_all: false,
1057 };
1058 let item = CallItem::Storage(storage);
1059 assert_eq!(item.name(), "Account");
1060 }
1061
1062 #[test]
1063 fn call_item_hint_works() {
1064 let function = Function {
1065 pallet: "System".to_string(),
1066 name: "remark".to_string(),
1067 ..Default::default()
1068 };
1069 let item = CallItem::Function(function);
1070 assert_eq!(item.hint(), "📝 [EXTRINSIC]");
1071
1072 let constant = Constant {
1073 pallet: "System".to_string(),
1074 name: "BlockHashCount".to_string(),
1075 docs: "docs".to_string(),
1076 value: Value::u128(250).map_context(|_| 0u32),
1077 };
1078 let item = CallItem::Constant(constant);
1079 assert_eq!(item.hint(), "[CONSTANT]");
1080
1081 let storage = Storage {
1082 pallet: "System".to_string(),
1083 name: "Account".to_string(),
1084 docs: "docs".to_string(),
1085 type_id: 42,
1086 key_id: None,
1087 query_all: false,
1088 };
1089 let item = CallItem::Storage(storage);
1090 assert_eq!(item.hint(), "[STORAGE]");
1091 }
1092
1093 #[test]
1094 fn call_item_docs_works() {
1095 let function = Function {
1096 pallet: "System".to_string(),
1097 name: "remark".to_string(),
1098 docs: "Make some on-chain remark.".to_string(),
1099 ..Default::default()
1100 };
1101 let item = CallItem::Function(function);
1102 assert_eq!(item.docs(), "Make some on-chain remark.");
1103
1104 let constant = Constant {
1105 pallet: "System".to_string(),
1106 name: "BlockHashCount".to_string(),
1107 docs: "Maximum number of block number to block hash mappings to keep.".to_string(),
1108 value: Value::u128(250).map_context(|_| 0u32),
1109 };
1110 let item = CallItem::Constant(constant);
1111 assert_eq!(item.docs(), "Maximum number of block number to block hash mappings to keep.");
1112
1113 let storage = Storage {
1114 pallet: "System".to_string(),
1115 name: "Account".to_string(),
1116 docs: "The full account information for a particular account ID.".to_string(),
1117 type_id: 42,
1118 key_id: None,
1119 query_all: false,
1120 };
1121 let item = CallItem::Storage(storage);
1122 assert_eq!(item.docs(), "The full account information for a particular account ID.");
1123 }
1124
1125 #[test]
1126 fn call_item_pallet_works() {
1127 let function = Function {
1128 pallet: "System".to_string(),
1129 name: "remark".to_string(),
1130 ..Default::default()
1131 };
1132 let item = CallItem::Function(function);
1133 assert_eq!(item.pallet(), "System");
1134
1135 let constant = Constant {
1136 pallet: "Balances".to_string(),
1137 name: "ExistentialDeposit".to_string(),
1138 docs: "docs".to_string(),
1139 value: Value::u128(100).map_context(|_| 0u32),
1140 };
1141 let item = CallItem::Constant(constant);
1142 assert_eq!(item.pallet(), "Balances");
1143
1144 let storage = Storage {
1145 pallet: "Timestamp".to_string(),
1146 name: "Now".to_string(),
1147 docs: "docs".to_string(),
1148 type_id: 10,
1149 key_id: None,
1150 query_all: false,
1151 };
1152 let item = CallItem::Storage(storage);
1153 assert_eq!(item.pallet(), "Timestamp");
1154 }
1155
1156 #[test]
1157 fn pallet_get_all_callables_works() {
1158 let function = Function {
1159 pallet: "System".to_string(),
1160 name: "remark".to_string(),
1161 ..Default::default()
1162 };
1163 let constant = Constant {
1164 pallet: "System".to_string(),
1165 name: "BlockHashCount".to_string(),
1166 docs: "docs".to_string(),
1167 value: Value::u128(250).map_context(|_| 0u32),
1168 };
1169 let storage = Storage {
1170 pallet: "System".to_string(),
1171 name: "Account".to_string(),
1172 docs: "docs".to_string(),
1173 type_id: 42,
1174 key_id: None,
1175 query_all: false,
1176 };
1177
1178 let pallet = Pallet {
1179 name: "System".to_string(),
1180 index: 0,
1181 docs: "System pallet".to_string(),
1182 functions: vec![function.clone()],
1183 constants: vec![constant.clone()],
1184 state: vec![storage.clone()],
1185 };
1186
1187 let callables = pallet.get_all_callables();
1188 assert_eq!(callables.len(), 3);
1189 assert!(matches!(callables[0], CallItem::Function(_)));
1190 assert!(matches!(callables[1], CallItem::Constant(_)));
1191 assert!(matches!(callables[2], CallItem::Storage(_)));
1192
1193 if let CallItem::Function(f) = &callables[0] {
1195 assert_eq!(f, &function);
1196 }
1197 if let CallItem::Constant(c) = &callables[1] {
1198 assert_eq!(c, &constant);
1199 }
1200 if let CallItem::Storage(s) = &callables[2] {
1201 assert_eq!(s, &storage);
1202 }
1203 }
1204
1205 #[test]
1206 fn find_callable_by_name_works() {
1207 let function = Function {
1208 pallet: "System".to_string(),
1209 name: "remark".to_string(),
1210 ..Default::default()
1211 };
1212 let constant = Constant {
1213 pallet: "System".to_string(),
1214 name: "BlockHashCount".to_string(),
1215 docs: "docs".to_string(),
1216 value: Value::u128(250).map_context(|_| 0u32),
1217 };
1218 let storage = Storage {
1219 pallet: "System".to_string(),
1220 name: "Account".to_string(),
1221 docs: "docs".to_string(),
1222 type_id: 42,
1223 key_id: None,
1224 query_all: false,
1225 };
1226
1227 let pallets = vec![Pallet {
1228 name: "System".to_string(),
1229 index: 0,
1230 docs: "System pallet".to_string(),
1231 functions: vec![function.clone()],
1232 constants: vec![constant.clone()],
1233 state: vec![storage.clone()],
1234 }];
1235
1236 let result = find_callable_by_name(&pallets, "System", "remark");
1238 assert!(result.is_ok());
1239 if let Ok(CallItem::Function(f)) = result {
1240 assert_eq!(f.name, "remark");
1241 }
1242
1243 let result = find_callable_by_name(&pallets, "System", "BlockHashCount");
1245 assert!(result.is_ok());
1246 if let Ok(CallItem::Constant(c)) = result {
1247 assert_eq!(c.name, "BlockHashCount");
1248 }
1249
1250 let result = find_callable_by_name(&pallets, "System", "Account");
1252 assert!(result.is_ok());
1253 if let Ok(CallItem::Storage(s)) = result {
1254 assert_eq!(s.name, "Account");
1255 }
1256
1257 let result = find_callable_by_name(&pallets, "System", "NonExistent");
1259 assert!(result.is_err());
1260 assert!(matches!(result.unwrap_err(), Error::FunctionNotFound(_)));
1261
1262 let result = find_callable_by_name(&pallets, "NonExistent", "remark");
1264 assert!(result.is_err());
1265 assert!(matches!(result.unwrap_err(), Error::PalletNotFound(_)));
1266 }
1267
1268 #[test]
1269 fn format_single_tuples_single_element_works() -> Result<()> {
1270 let inner_value = Value::u128(42);
1272 let single_tuple = Value::unnamed_composite(vec![inner_value]).map_context(|_| 0u32);
1273
1274 let mut output = String::new();
1275 let result = format_single_tuples(&single_tuple, &mut output);
1276
1277 assert!(result.is_some());
1279 assert!(result.unwrap().is_ok());
1280 assert_eq!(output, "42");
1281 Ok(())
1282 }
1283
1284 #[test]
1285 fn format_single_tuples_multi_element_returns_none() -> Result<()> {
1286 let tuple =
1288 Value::unnamed_composite(vec![Value::u128(1), Value::u128(2)]).map_context(|_| 0u32);
1289
1290 let mut output = String::new();
1291 let result = format_single_tuples(&tuple, &mut output);
1292
1293 assert!(result.is_none());
1295 assert_eq!(output, "");
1296 Ok(())
1297 }
1298
1299 #[test]
1300 fn format_single_tuples_empty_tuple_returns_none() -> Result<()> {
1301 let empty_tuple = Value::unnamed_composite(vec![]).map_context(|_| 0u32);
1303
1304 let mut output = String::new();
1305 let result = format_single_tuples(&empty_tuple, &mut output);
1306
1307 assert!(result.is_none());
1309 assert_eq!(output, "");
1310 Ok(())
1311 }
1312
1313 #[test]
1314 fn format_single_tuples_non_composite_returns_none() -> Result<()> {
1315 let simple_value = Value::u128(42).map_context(|_| 0u32);
1317
1318 let mut output = String::new();
1319 let result = format_single_tuples(&simple_value, &mut output);
1320
1321 assert!(result.is_none());
1323 assert_eq!(output, "");
1324 Ok(())
1325 }
1326
1327 #[test]
1328 fn format_single_tuples_named_composite_returns_none() -> Result<()> {
1329 let named_composite =
1331 Value::named_composite(vec![("field", Value::u128(42))]).map_context(|_| 0u32);
1332
1333 let mut output = String::new();
1334 let result = format_single_tuples(&named_composite, &mut output);
1335
1336 assert!(result.is_none());
1338 assert_eq!(output, "");
1339 Ok(())
1340 }
1341
1342 #[tokio::test]
1343 async fn query_storage_works() -> Result<()> {
1344 use crate::{parse_chain_metadata, set_up_client};
1345 use pop_common::test_env::shared_substrate_ws_url;
1346 use scale_value::stringify::custom_parsers;
1347
1348 let node_url = shared_substrate_ws_url().await;
1349 let client = set_up_client(&node_url).await?;
1350 let pallets = parse_chain_metadata(&client)?;
1351
1352 let storage = pallets
1354 .iter()
1355 .find(|p| p.name == "System")
1356 .and_then(|p| p.state.iter().find(|s| s.name == "Account"))
1357 .expect("System::Account storage should exist");
1358
1359 let alice_address = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
1361 let account_key = scale_value::stringify::from_str_custom()
1362 .add_custom_parser(custom_parsers::parse_ss58)
1363 .parse(alice_address)
1364 .0
1365 .expect("Should parse Alice's address");
1366
1367 let result = storage.query(&client, vec![account_key]).await?;
1368 assert!(result.is_some(), "Alice's account should exist in dev chain from genesis");
1369 Ok(())
1370 }
1371
1372 #[tokio::test]
1373 async fn query_storage_with_key_works() -> Result<()> {
1374 use crate::{parse_chain_metadata, set_up_client};
1375 use pop_common::test_env::shared_substrate_ws_url;
1376
1377 let node_url = shared_substrate_ws_url().await;
1378 let client = set_up_client(&node_url).await?;
1379 let pallets = parse_chain_metadata(&client)?;
1380
1381 let storage = pallets
1383 .iter()
1384 .find(|p| p.name == "System")
1385 .and_then(|p| p.state.iter().find(|s| s.name == "Account"))
1386 .expect("System::Account storage should exist");
1387
1388 let alice_address = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
1390 let account_key = scale_value::stringify::from_str_custom()
1391 .add_custom_parser(custom_parsers::parse_ss58)
1392 .parse(alice_address)
1393 .0
1394 .expect("Should parse Alice's address");
1395
1396 let result = storage.query(&client, vec![account_key]).await?;
1398
1399 assert!(result.is_some());
1401 Ok(())
1402 }
1403
1404 #[test]
1405 fn render_storage_key_values_with_keys_works() -> Result<()> {
1406 let key1 = Value::u128(42);
1408 let key2 = Value::string("test_key");
1409 let value = Value::bool(true).map_context(|_| 0u32);
1410
1411 let key_value_pairs = vec![(vec![key1, key2], value)];
1412
1413 let result = render_storage_key_values(&key_value_pairs)?;
1414
1415 let expected = "[\n 42,\n \"test_key\",\n true\n]\n";
1417 assert_eq!(result, expected);
1418 Ok(())
1419 }
1420
1421 #[test]
1422 fn render_storage_key_values_without_keys_works() -> Result<()> {
1423 let value = Value::u128(100).map_context(|_| 0u32);
1425
1426 let key_value_pairs = vec![(vec![], value)];
1427
1428 let result = render_storage_key_values(&key_value_pairs)?;
1429
1430 let expected = "[\n 100\n]\n";
1432 assert_eq!(result, expected);
1433 Ok(())
1434 }
1435}