use super::{
CLIChainOpts,
parse_account,
};
use crate::call_with_config;
use anyhow::Result;
use colored::Colorize;
use comfy_table::{
ContentArrangement,
Table,
};
use contract_extrinsics::{
ContractArtifacts,
ContractStorage,
ContractStorageLayout,
ContractStorageRpc,
ErrorVariant,
};
use ink_env::Environment;
use serde::Serialize;
use std::{
fmt::{
Debug,
Display,
},
path::PathBuf,
str::FromStr,
};
use subxt::{
Config,
config::HashFor,
ext::{
codec::Decode,
scale_decode::IntoVisitor,
},
};
#[derive(Debug, clap::Args)]
#[clap(name = "storage", about = "Inspect contract storage")]
pub struct StorageCommand {
#[clap(
name = "contract",
long,
env = "CONTRACT",
required_unless_present = "version"
)]
contract: Option<String>,
#[clap(long)]
raw: bool,
#[clap(name = "output-json", long, conflicts_with = "raw")]
output_json: bool,
#[clap(value_parser, conflicts_with = "manifest_path")]
file: Option<PathBuf>,
#[clap(long, value_parser)]
manifest_path: Option<PathBuf>,
#[clap(long, short)]
version: bool,
#[clap(flatten)]
chain_cli_opts: CLIChainOpts,
}
impl StorageCommand {
pub async fn handle(&self) -> Result<(), ErrorVariant> {
call_with_config!(self, run, self.chain_cli_opts.chain().config())
}
pub async fn run<C: Config + Environment>(&self) -> Result<(), ErrorVariant>
where
<C as Config>::AccountId: Display + IntoVisitor + AsRef<[u8]> + FromStr + Decode,
<<C as Config>::AccountId as FromStr>::Err:
Into<Box<dyn std::error::Error>> + Display + Debug,
C::Balance: Serialize + IntoVisitor + Debug,
HashFor<C>: IntoVisitor,
{
let rpc =
ContractStorageRpc::<C>::new(&self.chain_cli_opts.chain().url()).await?;
let storage_layout = ContractStorage::<C, C>::new(rpc);
if self.version {
println!("{}", storage_layout.version().await?);
return Ok(())
}
let contract = self
.contract
.as_ref()
.map(|c| parse_account(c))
.transpose()?
.expect("Contract argument shall be present");
if self.raw {
let storage_data =
storage_layout.load_contract_storage_data(&contract).await?;
println!(
"{json}",
json = serde_json::to_string_pretty(&storage_data)?
);
return Ok(())
}
let contract_artifacts = ContractArtifacts::from_manifest_or_file(
self.manifest_path.as_ref(),
self.file.as_ref(),
);
match contract_artifacts {
Ok(contract_artifacts) => {
let transcoder = contract_artifacts.contract_transcoder()?;
let contract_storage = storage_layout
.load_contract_storage_with_layout(&contract, &transcoder)
.await?;
if self.output_json {
println!(
"{json}",
json = serde_json::to_string_pretty(&contract_storage)?
);
} else {
let table = StorageDisplayTable::new(&contract_storage);
table.display();
}
}
Err(_) => {
eprintln!(
"{} Displaying raw storage: no valid contract metadata artifacts found",
"Info:".cyan().bold(),
);
let storage_data =
storage_layout.load_contract_storage_data(&contract).await?;
println!(
"{json}",
json = serde_json::to_string_pretty(&storage_data)?
);
}
}
Ok(())
}
}
struct StorageDisplayTable(Table);
impl StorageDisplayTable {
const INDEX_LABEL: &'static str = "Index";
const KEY_LABEL: &'static str = "Root Key";
const PARENT_LABEL: &'static str = "Parent";
const VALUE_LABEL: &'static str = "Value";
fn new(storage_layout: &ContractStorageLayout) -> Self {
let mut table = Table::new();
Self::table_add_header(&mut table);
Self::table_add_rows(&mut table, storage_layout);
Self(table)
}
fn table_add_header(table: &mut Table) {
table.set_content_arrangement(ContentArrangement::Dynamic);
let header = vec![
Self::INDEX_LABEL,
Self::KEY_LABEL,
Self::PARENT_LABEL,
Self::VALUE_LABEL,
];
table.set_header(header);
}
fn table_add_rows(table: &mut Table, storage_layout: &ContractStorageLayout) {
for (index, cell) in storage_layout.iter().enumerate() {
let formatted_cell = format!("{cell}");
let values = formatted_cell.split('\n');
for (i, v) in values.enumerate() {
table.add_row(vec![
(index + i).to_string().as_str(),
cell.root_key().as_str(),
cell.parent().as_str(),
v,
]);
}
}
}
fn display(&self) {
println!("{}", self.0);
}
}