use crate::call_with_config;
use super::{
CLIChainOpts,
basic_display_format_extended_contract_info,
display_all_contracts,
parse_addr,
};
use anyhow::Result;
use contract_analyze::determine_language;
use contract_extrinsics::{
CodeInfo,
ContractInfo,
ErrorVariant,
TrieId,
fetch_all_contracts,
fetch_code_info,
fetch_contract_binary,
fetch_contract_info,
resolve_h160,
url_to_string,
};
use ink_env::Environment;
use primitive_types::H256;
use serde::Serialize;
use std::{
fmt::{
Debug,
Display,
},
io::Write,
str::FromStr,
};
use subxt::{
Config,
OnlineClient,
backend::{
legacy::LegacyRpcMethods,
rpc::RpcClient,
},
config::HashFor,
ext::{
codec::Decode,
scale_decode::IntoVisitor,
},
};
#[derive(Debug, clap::Args)]
#[clap(name = "info", about = "Get infos from a contract")]
pub struct InfoCommand {
#[clap(
name = "contract",
long,
env = "CONTRACT",
required_unless_present = "all"
)]
contract: Option<String>,
#[clap(name = "output-json", long)]
output_json: bool,
#[clap(name = "binary", long, conflicts_with = "all")]
binary: bool,
#[clap(name = "all", long)]
all: bool,
#[clap(flatten)]
chain_cli_opts: CLIChainOpts,
}
impl InfoCommand {
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:
Serialize + Debug + Display + IntoVisitor + Decode + AsRef<[u8]> + FromStr,
HashFor<C>: IntoVisitor + Display,
<C as Environment>::Balance: Serialize + Debug + IntoVisitor,
<<C as Config>::AccountId as FromStr>::Err:
Into<Box<dyn std::error::Error>> + Display,
{
let rpc_cli =
RpcClient::from_url(url_to_string(&self.chain_cli_opts.chain().url()))
.await
.map_err(|e| subxt::Error::Rpc(e.into()))?;
let client = OnlineClient::<C>::from_rpc_client(rpc_cli.clone()).await?;
let rpc = LegacyRpcMethods::<C>::new(rpc_cli.clone());
if self.all {
let contracts = fetch_all_contracts(&client, &rpc).await?;
if self.output_json {
let contracts_json = serde_json::json!({
"Revive": contracts
});
println!("{}", serde_json::to_string_pretty(&contracts_json)?);
} else {
display_all_contracts(&contracts)
}
Ok(())
} else {
let contract_addr = self
.contract
.as_ref()
.map(|c| parse_addr(c))
.transpose()?
.expect("Contract argument shall be present");
let contract_info =
fetch_contract_info::<C, C>(&contract_addr, &rpc, &client).await?;
let code_info =
fetch_code_info::<C, C>(contract_info.code_hash(), &rpc, &client).await?;
let contract_binary =
fetch_contract_binary(&client, &rpc, contract_info.code_hash()).await?;
let account_id = resolve_h160::<C, C>(&contract_addr, &rpc, &client).await?;
let deposit_account_data =
contract_extrinsics::get_account_data::<C, C>(&account_id, &rpc, &client)
.await?;
if self.binary {
if self.output_json {
let output = serde_json::json!({
"contract_binary": format!("0x{}", hex::encode(contract_binary))
});
println!("{}", serde_json::to_string_pretty(&output)?);
} else {
std::io::stdout()
.write_all(&contract_binary)
.expect("Writing to stdout failed")
}
} else if self.output_json {
println!(
"{}",
serde_json::to_string_pretty(&ExtendedContractInfo::<
<C as Config>::AccountId,
C::Balance,
>::new(
contract_info,
code_info,
&contract_binary,
deposit_account_data
))?
)
} else {
basic_display_format_extended_contract_info(
&contract_addr,
&ExtendedContractInfo::<<C as Config>::AccountId, C::Balance>::new(
contract_info,
code_info,
&contract_binary,
deposit_account_data,
),
)
}
Ok(())
}
}
}
#[derive(serde::Serialize)]
pub struct ExtendedContractInfo<AccountId, Balance>
where
AccountId: serde::Serialize + Clone + Display + Debug + IntoVisitor,
Balance: serde::Serialize + Copy + Debug + IntoVisitor,
{
pub trie_id: TrieId,
pub code_hash: H256,
pub storage_bytes: u32,
pub storage_items: u32,
pub storage_byte_deposit: Balance,
pub storage_item_deposit: Balance,
pub storage_base_deposit: Balance,
pub immutable_data_len: u32,
pub storage_total_deposit: Balance,
pub source_language: String,
pub code_info: CodeInfo<AccountId, Balance>,
}
impl<AccountId, Balance> ExtendedContractInfo<AccountId, Balance>
where
AccountId: serde::Serialize + Clone + Display + Debug + IntoVisitor,
Balance: serde::Serialize + Copy + Debug + IntoVisitor,
{
pub fn new(
contract_info: ContractInfo<Balance>,
code_info: CodeInfo<AccountId, Balance>,
code: &[u8],
deposit_account_data: contract_extrinsics::AccountData<Balance>,
) -> Self {
let language = match determine_language(code).ok() {
Some(lang) => lang.to_string(),
None => "Unknown".to_string(),
};
ExtendedContractInfo {
trie_id: contract_info.trie_id().clone(),
code_hash: *contract_info.code_hash(),
storage_bytes: contract_info.storage_bytes(),
storage_items: contract_info.storage_items(),
storage_byte_deposit: contract_info.storage_byte_deposit(),
storage_item_deposit: contract_info.storage_item_deposit(),
storage_base_deposit: contract_info.storage_base_deposit(),
immutable_data_len: contract_info.immutable_data_len(),
storage_total_deposit: deposit_account_data.reserved,
source_language: language,
code_info,
}
}
}