mod call;
mod events;
mod instantiate;
mod runtime_api;
mod upload;
#[cfg(test)]
#[cfg(feature = "integration-tests")]
mod integration_tests;
use anyhow::{
anyhow,
Context,
Result,
};
use colored::Colorize;
use jsonrpsee::{
core::client::ClientT,
rpc_params,
ws_client::WsClientBuilder,
};
use std::{
io::{
self,
Write,
},
path::PathBuf,
};
use self::events::display_events;
use crate::{
crate_metadata::CrateMetadata,
name_value_println,
Verbosity,
VerbosityFlags,
DEFAULT_KEY_COL_WIDTH,
};
use pallet_contracts_primitives::ContractResult;
use scale::{
Decode,
Encode,
};
use sp_core::{
crypto::Pair,
sr25519,
Bytes,
};
use subxt::{
ext::sp_runtime::DispatchError,
tx,
Config,
OnlineClient,
};
pub use call::CallCommand;
pub use instantiate::InstantiateCommand;
pub use subxt::PolkadotConfig as DefaultConfig;
pub use transcode::ContractMessageTranscoder;
pub use upload::UploadCommand;
type Balance = u128;
type CodeHash = <DefaultConfig as Config>::Hash;
type ContractAccount = <DefaultConfig as Config>::AccountId;
type PairSigner = tx::PairSigner<DefaultConfig, sr25519::Pair>;
type Client = OnlineClient<DefaultConfig>;
#[derive(Clone, Debug, clap::Args)]
pub struct ExtrinsicOpts {
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[clap(
name = "url",
long,
parse(try_from_str),
default_value = "ws://localhost:9944"
)]
url: url::Url,
#[clap(name = "suri", long, short)]
suri: String,
#[clap(name = "password", long, short)]
password: Option<String>,
#[clap(flatten)]
verbosity: VerbosityFlags,
#[clap(long)]
dry_run: bool,
#[clap(long, parse(try_from_str = parse_balance))]
storage_deposit_limit: Option<Balance>,
#[clap(long)]
skip_dry_run: bool,
#[clap(long)]
skip_confirm: bool,
}
impl ExtrinsicOpts {
pub fn signer(&self) -> Result<sr25519::Pair> {
sr25519::Pair::from_string(&self.suri, self.password.as_ref().map(String::as_ref))
.map_err(|_| anyhow::anyhow!("Secret string error"))
}
pub fn verbosity(&self) -> Result<Verbosity> {
TryFrom::try_from(&self.verbosity)
}
pub fn url_to_string(&self) -> String {
let mut res = self.url.to_string();
match (self.url.port(), self.url.port_or_known_default()) {
(None, Some(port)) => {
res.insert_str(res.len() - 1, &format!(":{}", port));
res
}
_ => res,
}
}
}
fn parse_balance(input: &str) -> Result<Balance> {
input
.replace('_', "")
.parse::<Balance>()
.map_err(Into::into)
}
pub fn pair_signer(pair: sr25519::Pair) -> PairSigner {
PairSigner::new(pair)
}
const STORAGE_DEPOSIT_KEY: &str = "Storage Deposit";
pub const MAX_KEY_COL_WIDTH: usize = STORAGE_DEPOSIT_KEY.len() + 1;
pub fn display_contract_exec_result<R, const WIDTH: usize>(
result: &ContractResult<R, Balance>,
) -> Result<()> {
let mut debug_message_lines = std::str::from_utf8(&result.debug_message)
.context("Error decoding UTF8 debug message bytes")?
.lines();
name_value_println!("Gas Consumed", format!("{:?}", result.gas_consumed), WIDTH);
name_value_println!("Gas Required", format!("{:?}", result.gas_required), WIDTH);
name_value_println!(
STORAGE_DEPOSIT_KEY,
format!("{:?}", result.storage_deposit),
WIDTH
);
if let Some(debug_message) = debug_message_lines.next() {
name_value_println!("Debug Message", format!("{}", debug_message), WIDTH);
}
for debug_message in debug_message_lines {
name_value_println!("", format!("{}", debug_message), WIDTH);
}
Ok(())
}
async fn submit_extrinsic<T, Call>(
client: &OnlineClient<T>,
call: &Call,
signer: &(dyn tx::Signer<T> + Send + Sync),
) -> Result<tx::TxEvents<T>>
where
T: Config,
<T::ExtrinsicParams as tx::ExtrinsicParams<T::Index, T::Hash>>::OtherParams: Default,
Call: tx::TxPayload,
{
client
.tx()
.sign_and_submit_then_watch_default(call, signer)
.await?
.wait_for_in_block()
.await?
.wait_for_success()
.await
.map_err(Into::into)
}
async fn state_call<A: Encode, R: Decode>(url: &str, func: &str, args: A) -> Result<R> {
let cli = WsClientBuilder::default().build(&url).await?;
let params = rpc_params![func, Bytes(args.encode())];
let bytes: Bytes = cli.request("state_call", params).await?;
Ok(R::decode(&mut bytes.as_ref())?)
}
fn error_details(error: &DispatchError, metadata: &subxt::Metadata) -> Result<String> {
match error {
DispatchError::Module(err) => {
let details = metadata.error(err.index, err.error)?;
Ok(format!(
"ModuleError: {}::{}: {:?}",
details.pallet(),
details.error(),
details.docs()
))
}
err => Ok(format!("DispatchError: {:?}", err)),
}
}
fn prompt_confirm_tx<F: FnOnce()>(show_details: F) -> Result<()> {
println!(
"{} (skip with --skip-confirm)",
"Confirm transaction details:".bright_white().bold()
);
show_details();
print!(
"{} ({}/n): ",
"Submit?".bright_white().bold(),
"Y".bright_white().bold()
);
let mut buf = String::new();
io::stdout().flush()?;
io::stdin().read_line(&mut buf)?;
match buf.trim().to_lowercase().as_str() {
"y" | "" => Ok(()),
"n" => Err(anyhow!("Transaction not submitted")),
c => Err(anyhow!("Expected either 'y' or 'n', got '{}'", c)),
}
}
fn print_dry_running_status(msg: &str) {
println!(
"{:>width$} {} (skip with --skip-dry-run)",
"Dry-running".green().bold(),
msg.bright_white().bold(),
width = DEFAULT_KEY_COL_WIDTH
);
}
fn print_gas_required_success(gas: u64) {
println!(
"{:>width$} Gas required estimated at {}",
"Success!".green().bold(),
gas.to_string().bright_white(),
width = DEFAULT_KEY_COL_WIDTH
);
}