#![deny(unused_crate_dependencies)]
mod cmd;
use self::cmd::{
AccountCommand,
BuildCommand,
CallCommand,
CheckCommand,
DecodeCommand,
ErrorVariant,
GenerateSchemaCommand,
InfoCommand,
InstantiateCommand,
LintCommand,
RemoveCommand,
RpcCommand,
StorageCommand,
UploadCommand,
VerifyCommand,
VerifySchemaCommand,
};
use anyhow::{
anyhow,
Error,
Result,
};
use clap::{
Args,
Parser,
Subcommand,
};
use cmd::encode::EncodeCommand;
use colored::Colorize;
use contract_build::{
util::DEFAULT_KEY_COL_WIDTH,
OutputType,
};
use contract_extrinsics::InstantiateExec;
use sp_weights::Weight;
use std::{
fmt::Debug,
path::PathBuf,
str::FromStr,
};
use tokio::runtime::Runtime;
#[cfg(test)]
use assert_cmd as _;
#[cfg(test)]
use predicates as _;
#[cfg(test)]
use regex as _;
#[cfg(test)]
use tempfile as _;
use which as _;
use deranged as _;
#[derive(Debug, Parser)]
#[clap(bin_name = "cargo")]
#[clap(version = env!("CARGO_CONTRACT_CLI_IMPL_VERSION"))]
pub(crate) enum Opts {
#[clap(name = "contract")]
#[clap(version = env!("CARGO_CONTRACT_CLI_IMPL_VERSION"))]
#[clap(action = ArgAction::DeriveDisplayOrder)]
Contract(ContractArgs),
}
#[derive(Debug, Args)]
pub(crate) struct ContractArgs {
#[clap(subcommand)]
cmd: Command,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct HexData(pub Vec<u8>);
impl FromStr for HexData {
type Err = hex::FromHexError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
hex::decode(input).map(HexData)
}
}
#[derive(Debug, Subcommand)]
enum Command {
#[clap(name = "new")]
New {
name: String,
#[clap(short, long, value_parser)]
target_dir: Option<PathBuf>,
},
#[clap(name = "build")]
Build(BuildCommand),
#[clap(name = "check")]
Check(CheckCommand),
#[clap(name = "upload")]
Upload(UploadCommand),
#[clap(name = "instantiate")]
Instantiate(InstantiateCommand),
#[clap(name = "call")]
Call(CallCommand),
#[clap(name = "account")]
Account(AccountCommand),
#[clap(name = "encode")]
Encode(EncodeCommand),
#[clap(name = "decode")]
Decode(DecodeCommand),
#[clap(name = "remove")]
Remove(RemoveCommand),
#[clap(name = "info")]
Info(InfoCommand),
#[clap(name = "storage")]
Storage(StorageCommand),
#[clap(name = "verify")]
Verify(VerifyCommand),
#[clap(name = "generate-schema")]
GenerateSchema(GenerateSchemaCommand),
#[clap(name = "verify-schema")]
VerifySchema(VerifySchemaCommand),
#[clap(name = "rpc")]
Rpc(RpcCommand),
#[clap(name = "lint")]
Lint(LintCommand),
}
fn main() {
tracing_subscriber::fmt::init();
let Opts::Contract(args) = Opts::parse();
match exec(args.cmd) {
Ok(()) => {}
Err(err) => {
eprintln!("{err:?}");
std::process::exit(1);
}
}
}
fn exec(cmd: Command) -> Result<()> {
let runtime = Runtime::new().expect("Failed to create Tokio runtime");
match &cmd {
Command::New { name, target_dir } => {
contract_build::new_contract_project(name, target_dir.as_ref())?;
println!("Created contract {name}");
Ok(())
}
Command::Build(build) => {
let result = build.exec().map_err(format_err)?;
if matches!(result.output_type, OutputType::Json) {
println!("{}", result.serialize_json()?)
} else if result.verbosity.is_verbose() {
println!("{}", result.display())
}
Ok(())
}
Command::Check(check) => {
let res = check.exec().map_err(format_err)?;
assert!(
res.dest_binary.is_none(),
"no dest_binary must be on the generation result"
);
Ok(())
}
Command::Upload(upload) => {
runtime.block_on(async {
upload
.handle()
.await
.map_err(|err| map_extrinsic_err(err, upload.output_json()))
})
}
Command::Instantiate(instantiate) => {
runtime.block_on(async {
instantiate
.handle()
.await
.map_err(|err| map_extrinsic_err(err, instantiate.output_json()))
})
}
Command::Call(call) => {
runtime.block_on(async {
call.handle()
.await
.map_err(|err| map_extrinsic_err(err, call.output_json()))
})
}
Command::Encode(encode) => encode.run().map_err(format_err),
Command::Decode(decode) => decode.run().map_err(format_err),
Command::Remove(remove) => {
runtime.block_on(async {
remove
.handle()
.await
.map_err(|err| map_extrinsic_err(err, remove.output_json()))
})
}
Command::Account(account) => {
runtime.block_on(async { account.handle().await.map_err(format_err) })
}
Command::Info(info) => {
runtime.block_on(async { info.handle().await.map_err(format_err) })
}
Command::Storage(storage) => {
runtime.block_on(async { storage.handle().await.map_err(format_err) })
}
Command::Verify(verify) => {
let result = verify.run().map_err(format_err)?;
if result.output_json {
println!("{}", result.serialize_json()?)
} else if result.verbosity.is_verbose() {
println!("{}", result.display())
}
Ok(())
}
Command::GenerateSchema(generate) => {
let result = generate.run().map_err(format_err)?;
println!("{}", result);
Ok(())
}
Command::VerifySchema(verify) => {
let result = verify.run().map_err(format_err)?;
if result.output_json {
println!("{}", result.serialize_json()?)
} else if result.verbosity.is_verbose() {
println!("{}", result.display())
}
Ok(())
}
Command::Rpc(rpc) => {
runtime.block_on(async { rpc.run().await.map_err(format_err) })
}
Command::Lint(lint) => lint.run().map_err(format_err),
}
}
fn map_extrinsic_err(err: ErrorVariant, is_json: bool) -> Error {
if is_json {
anyhow!(
"{}",
serde_json::to_string_pretty(&err)
.expect("error serialization is infallible; qed")
)
} else {
format_err(err)
}
}
fn format_err<E: Debug>(err: E) -> Error {
anyhow!(
"{} {}",
"ERROR:".bright_red().bold(),
format!("{err:?}").bright_red()
)
}