use clap::Args;
use color_eyre::{eyre::bail, owo_colors::OwoColorize};
use indoc::{formatdoc, writedoc};
use scale_typegen_description::type_description;
use scale_value::Value;
use std::fmt::Write;
use std::write;
use subxt::metadata::{ArcMetadata, PalletMetadata, StorageMetadata};
use crate::utils::{
FileOrUrl, Indent, SyntaxHighlight, create_client, first_paragraph_of_docs,
parse_string_into_scale_value, type_example,
};
#[derive(Debug, Clone, Args)]
pub struct StorageSubcommand {
storage_entry: Option<String>,
#[clap(long, short, action)]
execute: bool,
#[clap(required = false)]
trailing_args: Vec<String>,
}
pub async fn explore_storage(
command: StorageSubcommand,
pallet_metadata: PalletMetadata<'_>,
metadata: ArcMetadata,
file_or_url: FileOrUrl,
output: &mut impl std::io::Write,
) -> color_eyre::Result<()> {
let pallet_name = pallet_metadata.name();
let trailing_args = command.trailing_args;
let Some(storage_metadata) = pallet_metadata.storage() else {
writeln!(
output,
"The \"{pallet_name}\" pallet has no storage entries."
)?;
return Ok(());
};
let storage_entry_placeholder = "<STORAGE_ENTRY>".blue();
let usage = || {
let storage_entries = storage_entries_string(storage_metadata, pallet_name);
formatdoc! {"
Usage:
subxt explore pallet {pallet_name} storage {storage_entry_placeholder}
explore a specific storage entry of this pallet
{storage_entries}
"}
};
let Some(entry_name) = command.storage_entry else {
writeln!(output, "{}", usage())?;
return Ok(());
};
let Some(storage) = storage_metadata
.entries()
.iter()
.find(|entry| entry.name().eq_ignore_ascii_case(&entry_name))
else {
bail!(
"Storage entry \"{entry_name}\" not found in \"{pallet_name}\" pallet!\n\n{}",
usage()
);
};
let return_ty_id = storage.value_ty();
let key_value_placeholder = "<KEY_VALUE>".blue();
let docs_string = first_paragraph_of_docs(storage.docs()).indent(4);
if !docs_string.is_empty() {
writedoc! {output, "
Description:
{docs_string}
"}?;
}
if !command.execute {
writedoc! {output, "
Usage:
subxt explore pallet {pallet_name} storage {entry_name} --execute {key_value_placeholder}
retrieve a value from storage
"}?;
}
let return_ty_description = type_description(return_ty_id, metadata.types(), true)
.expect("No type Description")
.indent(4)
.highlight();
writedoc! {output, "
The storage entry has the following shape:
{return_ty_description}
"}?;
let storage_keys = storage.keys().collect::<Vec<_>>();
if !storage_keys.is_empty() {
let key_ty_description = format!(
"({})",
storage_keys
.iter()
.map(|key| type_description(key.key_id, metadata.types(), true)
.expect("No type Description"))
.collect::<Vec<_>>()
.join(", ")
)
.indent(4)
.highlight();
let key_ty_example = format!(
"({})",
storage_keys
.iter()
.map(|key| type_example(key.key_id, metadata.types()).to_string())
.collect::<Vec<_>>()
.join(", ")
)
.indent(4)
.highlight();
writedoc! {output, "
The {key_value_placeholder} has the following shape:
{key_ty_description}
For example you could provide this {key_value_placeholder}:
{key_ty_example}
"}?;
} else {
writedoc! {output,"
Can be accessed without providing a {key_value_placeholder}.
"}?;
}
if !command.execute {
return Ok(());
}
let storage_entry_keys: Vec<Value> = match (!trailing_args.is_empty(), !storage_keys.is_empty())
{
(true, false) => {
let trailing_args_str = trailing_args.join(" ");
let warning = format!(
"Warning: You submitted one or more keys \"{trailing_args_str}\", but no key is needed. To access the storage value, please do not provide any keys."
);
writeln!(output, "{}", warning.yellow())?;
return Ok(());
}
(false, true) => {
return Ok(());
}
(false, false) => vec![],
(true, true) => {
let values = trailing_args
.iter()
.map(|arg| parse_string_into_scale_value(arg))
.collect::<color_eyre::Result<Vec<_>>>()?;
let values_str = values
.iter()
.map(|v| v.to_string().highlight())
.collect::<Vec<_>>()
.join("\n");
let value_str = values_str.indent(4);
writedoc! {output, "
You submitted the following {key_value_placeholder}:
{value_str}
"}?;
values
}
};
let client = create_client(&file_or_url).await?;
let storage_value = client
.at_current_block()
.await?
.storage()
.fetch((pallet_name, storage.name()), storage_entry_keys)
.await?
.decode()?;
let value = storage_value.to_string().highlight();
writedoc! {output, "
The value of the storage entry is:
{value}
"}?;
Ok(())
}
fn storage_entries_string(storage_metadata: &StorageMetadata, pallet_name: &str) -> String {
let storage_entry_placeholder = "<STORAGE_ENTRY>".blue();
if storage_metadata.entries().is_empty() {
format!("No {storage_entry_placeholder}'s available in the \"{pallet_name}\" pallet.")
} else {
let mut output =
format!("Available {storage_entry_placeholder}'s in the \"{pallet_name}\" pallet:");
let mut strings: Vec<_> = storage_metadata
.entries()
.iter()
.map(|s| s.name())
.collect();
strings.sort();
for entry in strings {
write!(output, "\n {entry}").unwrap();
}
output
}
}