pop_chains/call/metadata/
mod.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use 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
36// Formats to hexadecimal in lowercase
37fn 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
51/// Converts a raw SCALE value to a human-readable string representation.
52///
53/// This function takes a raw SCALE value and formats it into a string using custom formatters:
54/// - Formats byte sequences as hex strings.
55/// - Unwraps single-element tuples.
56/// - Uses pretty printing for better readability.
57///
58/// # Arguments
59/// * `value` - The raw SCALE value to convert to string.
60///
61/// # Returns
62/// * `Ok(String)` - The formatted string representation of the value.
63/// * `Err(_)` - If the value cannot be converted to string.
64pub 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	// Add indentation to each line
74	let indented = result
75		.lines()
76		.map(|line| format!("{indent}{line}"))
77		.collect::<Vec<_>>()
78		.join("\n");
79	Ok(indented)
80}
81
82/// Renders storage key-value pairs into a human-readable string format.
83///
84/// Takes a slice of tuples containing storage keys and their associated values and formats them
85/// into a readable string representation. Each key-value pair is rendered on separate lines within
86/// square brackets.
87///
88/// # Arguments
89/// * `key_value_pairs` - A slice of tuples where each tuple contains:
90///   - A vector of storage keys.
91///   - The associated storage value.
92///
93/// # Returns
94/// * `Ok(String)` - A formatted string containing the rendered key-value pairs.
95/// * `Err(_)` - If there's an error converting the values to strings.
96pub 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/// Represents different types of callable items that can be interacted with in the runtime.
116#[derive(Clone, Debug, Eq, PartialEq)]
117pub enum CallItem {
118	/// A dispatchable function (extrinsic) that can be called.
119	Function(Function),
120	/// A constant value defined in the runtime.
121	Constant(Constant),
122	/// A storage item that can be queried.
123	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	/// Returns a reference to the [`Function`] if this is a function call item.
144	pub fn as_function(&self) -> Option<&Function> {
145		match self {
146			CallItem::Function(f) => Some(f),
147			_ => None,
148		}
149	}
150
151	/// Returns a reference to the [`Constant`] if this is a constant call item.
152	pub fn as_constant(&self) -> Option<&Constant> {
153		match self {
154			CallItem::Constant(c) => Some(c),
155			_ => None,
156		}
157	}
158
159	/// Returns a reference to the [`Storage`] if this is a storage call item.
160	pub fn as_storage(&self) -> Option<&Storage> {
161		match self {
162			CallItem::Storage(s) => Some(s),
163			_ => None,
164		}
165	}
166
167	/// Returns the name of this call item.
168	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	/// Returns a descriptive hint string indicating the type of this call item.
176	pub fn hint(&self) -> &str {
177		match self {
178			CallItem::Function(_) => "📝 [EXTRINSIC]",
179			CallItem::Constant(_) => "[CONSTANT]",
180			CallItem::Storage(_) => "[STORAGE]",
181		}
182	}
183
184	/// Returns the documentation string associated with this call item.
185	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	/// Returns the name of the pallet containing this call item.
194	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/// Represents a pallet in the blockchain, including its dispatchable functions.
204#[derive(Clone, Debug, Default, Eq, PartialEq)]
205pub struct Pallet {
206	/// The name of the pallet.
207	pub name: String,
208	/// The index of the pallet within the runtime.
209	pub index: u8,
210	/// The documentation of the pallet.
211	pub docs: String,
212	/// The dispatchable functions of the pallet.
213	pub functions: Vec<Function>,
214	/// The constants of the pallet.
215	pub constants: Vec<Constant>,
216	/// The storage items of the pallet.
217	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	/// Returns a vector containing all callable items (functions, constants, and storage) defined
228	/// in this pallet.
229	///
230	/// This method collects and returns all available callable items from the pallet:
231	/// - Dispatchable functions (extrinsics)
232	/// - Constants
233	/// - Storage items
234	///
235	/// # Returns
236	/// A `Vec<CallItem>` containing all callable items from this pallet.
237	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/// Represents a dispatchable function.
253#[derive(Clone, Debug, Default, Eq, PartialEq)]
254pub struct Function {
255	/// The pallet containing the dispatchable function.
256	pub pallet: String,
257	/// The name of the function.
258	pub name: String,
259	/// The index of the function within the pallet.
260	pub index: u8,
261	/// The documentation of the function.
262	pub docs: String,
263	/// The parameters of the function.
264	pub params: Vec<Param>,
265	/// Whether this function is supported (no recursive or unsupported types like `RuntimeCall`).
266	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/// Represents a runtime constant.
276#[derive(Clone, Debug, Eq, PartialEq)]
277pub struct Constant {
278	/// The pallet containing the dispatchable function.
279	pub pallet: String,
280	/// The name of the constant.
281	pub name: String,
282	/// The documentation of the constant.
283	pub docs: String,
284	/// The value of the constant.
285	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/// Represents a storage item.
295#[derive(Clone, Debug, Eq, PartialEq)]
296pub struct Storage {
297	/// The pallet containing the storage item.
298	pub pallet: String,
299	/// The name of the storage item.
300	pub name: String,
301	/// The documentation of the storage item.
302	pub docs: String,
303	/// The type ID for decoding the storage value.
304	pub type_id: u32,
305	/// Optional type ID for map-type storage items. Usually a tuple.
306	pub key_id: Option<u32>,
307	/// Whether to query all values for a storage item, optionally filtered by provided keys.
308	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	/// Queries all values for a storage item, optionally filtered by provided keys.
319	///
320	/// This method allows retrieving multiple values from storage by iterating through all entries
321	/// that match the provided keys. For map-type storage items, keys can be used to filter
322	/// the results.
323	///
324	/// # Arguments
325	/// * `client` - The client to interact with the chain.
326	/// * `keys` - Optional storage keys for map-type storage items to filter results.
327	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	/// Query the storage value from the chain and return it as a formatted string.
361	///
362	/// # Arguments
363	/// * `client` - The client to interact with the chain.
364	/// * `keys` - Optional storage keys for map-type storage items.
365	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		// Decode the value if it exists
385		match storage_data {
386			Some(value) => {
387				// Try to decode using the type information
388				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			// Decode the SCALE-encoded constant value using its type information
450			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					// Parse parameters for the dispatchable function.
493					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									// If an error occurs while parsing the values, mark the
500									// dispatchable function as unsupported rather than
501									// error.
502									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							// Filter out blank lines and then flatten into a single value.
516							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							// To display the message in the UI
527							"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
539/// Parses the chain metadata to extract information about pallets and their dispatchable functions.
540///
541/// # Arguments
542/// * `client`: The client to interact with the chain.
543///
544/// NOTE: pallets are ordered by their index within the runtime by default.
545pub 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
565/// Finds a specific pallet by name and retrieves its details from metadata.
566///
567/// # Arguments
568/// * `pallets`: List of pallets available within the chain's runtime.
569/// * `pallet_name`: The name of the pallet to find.
570pub 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
581/// Finds a specific dispatchable function by name and retrieves its details from metadata.
582///
583/// # Arguments
584/// * `pallets`: List of pallets available within the chain's runtime.
585/// * `pallet_name`: The name of the pallet.
586/// * `function_name`: Name of the dispatchable function to locate.
587pub 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
607/// Parses and processes raw string parameter values for a dispatchable function, mapping them to
608/// `Value` types.
609///
610/// # Arguments
611/// * `params`: The metadata definition for each parameter of the corresponding dispatchable
612///   function.
613/// * `raw_params`: A vector of raw string arguments for the dispatchable function.
614pub 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			// Convert sequence parameters to hex if is_sequence
623			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		// Values for testing from: https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str.html
648		// and https://docs.rs/scale-value/0.18.0/scale_value/stringify/fn.from_str_custom.html
649		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		// Define mock dispatchable function parameters for testing.
672		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(&params, 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		// Create a test value using map_context to convert Value<()> to Value<u32>
758		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		// Test storage without key_id (plain storage)
788		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		// Test storage with key_id (storage map)
801		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		// Test simple integer value
817		let value = Value::u128(250).map_context(|_| 0u32);
818		let result = raw_value_to_string(&value, "")?;
819		assert_eq!(result, "250");
820
821		// Test boolean value
822		let value = Value::bool(true).map_context(|_| 0u32);
823		let result = raw_value_to_string(&value, "")?;
824		assert_eq!(result, "true");
825
826		// Test string value
827		let value = Value::string("hello").map_context(|_| 0u32);
828		let result = raw_value_to_string(&value, "")?;
829		assert_eq!(result, "\"hello\"");
830
831		// Test single-element tuple (should unwrap) - demonstrates format_single_tuples
832		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"); // 42 in hex - unwrapped from tuple
836
837		// Test multi-element composite - hex formatted
838		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"); // Formatted as hex bytes
842
843		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		// Verify the items match
1086		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		// Test finding a function
1129		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		// Test finding a constant
1136		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		// Test finding a storage item
1143		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		// Test not finding a callable
1150		let result = find_callable_by_name(&pallets, "System", "NonExistent");
1151		assert!(result.is_err());
1152		assert!(matches!(result.unwrap_err(), Error::FunctionNotFound(_)));
1153
1154		// Test pallet not found
1155		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		// Create a single-element tuple
1163		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		// Should return Some(Ok(())) and unwrap the tuple
1170		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		// Create a multi-element tuple
1179		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		// Should return None for multi-element tuples
1186		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		// Create an empty tuple
1194		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		// Should return None for empty tuples
1200		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		// Create a non-composite value (not a tuple)
1208		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		// Should return None for non-composite values
1214		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		// Create a named composite (not an unnamed tuple)
1222		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		// Should return None for named composites
1229		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		// Spawn a test node
1240		let node = TestNode::spawn().await?;
1241		let client = set_up_client(node.ws_url()).await?;
1242		let pallets = parse_chain_metadata(&client)?;
1243
1244		// Find a storage item (System::Number is a simple storage item that always exists)
1245		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		// Query the storage (without keys for plain storage)
1252		let result = storage.query(&client, vec![]).await?;
1253
1254		// Should return Some value (block number)
1255		assert!(result.is_some());
1256		let value = result.unwrap();
1257		// The value should be decodable as a block number (u32 or u64)
1258		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		// Spawn a test node
1268		let node = TestNode::spawn().await?;
1269		let client = set_up_client(node.ws_url()).await?;
1270		let pallets = parse_chain_metadata(&client)?;
1271
1272		// Find a map storage item (System::Account requires a key)
1273		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		// Use Alice's account as the key
1280		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		// Query the storage with the account key
1288		let result = storage.query(&client, vec![account_key]).await?;
1289
1290		// Should return Some value for Alice's account (which should exist in a test chain)
1291		assert!(result.is_some());
1292		Ok(())
1293	}
1294
1295	#[test]
1296	fn render_storage_key_values_with_keys_works() -> Result<()> {
1297		// Create test data with keys
1298		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		// Expected format with keys
1307		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		// Create test data without keys (empty key vector)
1315		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		// Expected format without keys
1322		let expected = "[\n  100\n]\n";
1323		assert_eq!(result, expected);
1324		Ok(())
1325	}
1326}