mod cmd;
mod crate_metadata;
mod util;
mod workspace;
use self::workspace::ManifestPath;
#[cfg(feature = "extrinsics")]
use sp_core::{crypto::Pair, sr25519, H256};
use std::{
convert::{TryFrom, TryInto},
path::PathBuf,
};
#[cfg(feature = "extrinsics")]
use subxt::PairSigner;
use anyhow::{Error, Result};
use colored::Colorize;
use structopt::{clap, StructOpt};
#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
pub(crate) enum Opts {
#[structopt(name = "contract")]
#[structopt(setting = clap::AppSettings::UnifiedHelpMessage)]
#[structopt(setting = clap::AppSettings::DeriveDisplayOrder)]
#[structopt(setting = clap::AppSettings::DontCollapseArgsInUsage)]
Contract(ContractArgs),
}
#[derive(Debug, StructOpt)]
pub(crate) struct ContractArgs {
#[structopt(subcommand)]
cmd: Command,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct HexData(pub Vec<u8>);
#[cfg(feature = "extrinsics")]
impl std::str::FromStr for HexData {
type Err = hex::FromHexError;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
hex::decode(input).map(HexData)
}
}
#[cfg(feature = "extrinsics")]
#[derive(Debug, StructOpt)]
pub(crate) struct ExtrinsicOpts {
#[structopt(
name = "url",
long,
parse(try_from_str),
default_value = "ws://localhost:9944"
)]
url: url::Url,
#[structopt(name = "suri", long, short)]
suri: String,
#[structopt(name = "password", long, short)]
password: Option<String>,
}
#[cfg(feature = "extrinsics")]
impl ExtrinsicOpts {
pub fn signer(&self) -> Result<PairSigner<subxt::DefaultNodeRuntime, sr25519::Pair>> {
let pair =
sr25519::Pair::from_string(&self.suri, self.password.as_ref().map(String::as_ref))
.map_err(|_| anyhow::anyhow!("Secret string error"))?;
Ok(PairSigner::new(pair))
}
}
#[derive(Debug, StructOpt)]
struct VerbosityFlags {
#[structopt(long)]
quiet: bool,
#[structopt(long)]
verbose: bool,
}
#[derive(Clone, Copy)]
enum Verbosity {
Quiet,
Verbose,
}
impl TryFrom<&VerbosityFlags> for Option<Verbosity> {
type Error = Error;
fn try_from(value: &VerbosityFlags) -> Result<Self, Self::Error> {
match (value.quiet, value.verbose) {
(false, false) => Ok(None),
(true, false) => Ok(Some(Verbosity::Quiet)),
(false, true) => Ok(Some(Verbosity::Verbose)),
(true, true) => anyhow::bail!("Cannot pass both --quiet and --verbose flags"),
}
}
}
#[derive(Debug, StructOpt)]
struct UnstableOptions {
#[structopt(long = "unstable-options", short = "Z", number_of_values = 1)]
options: Vec<String>,
}
#[derive(Clone, Default)]
struct UnstableFlags {
original_manifest: bool,
}
impl TryFrom<&UnstableOptions> for UnstableFlags {
type Error = Error;
fn try_from(value: &UnstableOptions) -> Result<Self, Self::Error> {
let valid_flags = ["original-manifest"];
let invalid_flags = value
.options
.iter()
.filter(|o| !valid_flags.contains(&o.as_str()))
.collect::<Vec<_>>();
if !invalid_flags.is_empty() {
anyhow::bail!("Unknown unstable-options {:?}", invalid_flags)
}
Ok(UnstableFlags {
original_manifest: value.options.contains(&"original-manifest".to_owned()),
})
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, StructOpt)]
#[structopt(name = "build-artifacts")]
pub enum GenerateArtifacts {
#[structopt(name = "all")]
All,
#[structopt(name = "code-only")]
CodeOnly,
}
impl GenerateArtifacts {
pub fn steps(&self) -> usize {
match self {
GenerateArtifacts::All => 5,
GenerateArtifacts::CodeOnly => 3,
}
}
pub fn display(&self, result: &GenerationResult) -> String {
let optimization = GenerationResult::display_optimization(result);
let size_diff = format!(
"\nOriginal wasm size: {}, Optimized: {}\n\n",
format!("{:.1}K", optimization.0).bold(),
format!("{:.1}K", optimization.1).bold(),
);
if self == &GenerateArtifacts::CodeOnly {
let out = format!(
"{}Your contract's code is ready. You can find it here:\n{}",
size_diff,
result
.dest_wasm
.as_ref()
.expect("wasm path must exist")
.display()
.to_string()
.bold()
);
return out;
};
let mut out = format!(
"{}Your contract artifacts are ready. You can find them in:\n{}\n\n",
size_diff,
result.target_directory.display().to_string().bold(),
);
if let Some(dest_bundle) = result.dest_bundle.as_ref() {
let bundle = format!(
" - {} (code + metadata)\n",
GenerationResult::display(&dest_bundle).bold()
);
out.push_str(&bundle);
}
if let Some(dest_wasm) = result.dest_wasm.as_ref() {
let wasm = format!(
" - {} (the contract's code)\n",
GenerationResult::display(&dest_wasm).bold()
);
out.push_str(&wasm);
}
if let Some(dest_metadata) = result.dest_metadata.as_ref() {
let metadata = format!(
" - {} (the contract's metadata)",
GenerationResult::display(&dest_metadata).bold()
);
out.push_str(&metadata);
}
out
}
}
impl std::str::FromStr for GenerateArtifacts {
type Err = String;
fn from_str(artifact: &str) -> Result<Self, Self::Err> {
match artifact {
"all" => Ok(GenerateArtifacts::All),
"code-only" => Ok(GenerateArtifacts::CodeOnly),
_ => Err("Could not parse build artifact".to_string()),
}
}
}
pub struct GenerationResult {
pub dest_metadata: Option<PathBuf>,
pub dest_wasm: Option<PathBuf>,
pub dest_bundle: Option<PathBuf>,
pub target_directory: PathBuf,
pub optimization_result: Option<OptimizationResult>,
}
pub struct OptimizationResult {
pub original_size: f64,
pub optimized_size: f64,
}
impl GenerationResult {
pub fn display(path: &PathBuf) -> &str {
path.file_name()
.expect("file name must exist")
.to_str()
.expect("must be valid utf-8")
}
pub fn display_optimization(res: &GenerationResult) -> (f64, f64) {
let optimization = res
.optimization_result
.as_ref()
.expect("optimization result must exist");
(optimization.original_size, optimization.optimized_size)
}
}
#[derive(Debug, StructOpt)]
enum Command {
#[structopt(name = "new")]
New {
name: String,
#[structopt(short, long, parse(from_os_str))]
target_dir: Option<PathBuf>,
},
#[structopt(name = "build")]
Build {
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[structopt(
long = "generate",
default_value = "all",
value_name = "all | code-only",
verbatim_doc_comment
)]
build_artifact: GenerateArtifacts,
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
},
#[structopt(name = "generate-metadata")]
GenerateMetadata {},
#[structopt(name = "check")]
Check {
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
},
#[structopt(name = "test")]
Test {},
#[cfg(feature = "extrinsics")]
#[structopt(name = "deploy")]
Deploy {
#[structopt(flatten)]
extrinsic_opts: ExtrinsicOpts,
#[structopt(parse(from_os_str))]
wasm_path: Option<PathBuf>,
},
#[cfg(feature = "extrinsics")]
#[structopt(name = "instantiate")]
Instantiate {
#[structopt(flatten)]
extrinsic_opts: ExtrinsicOpts,
#[structopt(name = "endowment", long, default_value = "0")]
endowment: u128,
#[structopt(name = "gas", long, default_value = "500000000")]
gas_limit: u64,
#[structopt(long, parse(try_from_str = parse_code_hash))]
code_hash: H256,
#[structopt(long)]
data: HexData,
},
}
#[cfg(feature = "extrinsics")]
fn parse_code_hash(input: &str) -> Result<H256> {
let bytes = hex::decode(input)?;
if bytes.len() != 32 {
anyhow::bail!("Code hash should be 32 bytes in length")
}
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(H256(arr))
}
fn main() {
env_logger::init();
let Opts::Contract(args) = Opts::from_args();
match exec(args.cmd) {
Ok(msg) => println!("\t{}", msg),
Err(err) => {
eprintln!(
"{} {}",
"ERROR:".bright_red().bold(),
format!("{:?}", err).bright_red()
);
std::process::exit(1);
}
}
}
fn exec(cmd: Command) -> Result<String> {
match &cmd {
Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()),
Command::Build {
manifest_path,
verbosity,
build_artifact,
unstable_options,
} => {
let manifest_path = ManifestPath::try_from(manifest_path.as_ref())?;
let result = cmd::build::execute(
&manifest_path,
verbosity.try_into()?,
true,
*build_artifact,
unstable_options.try_into()?,
)?;
Ok(build_artifact.display(&result))
}
Command::Check {
manifest_path,
verbosity,
unstable_options,
} => {
let manifest_path = ManifestPath::try_from(manifest_path.as_ref())?;
let res = cmd::build::execute(
&manifest_path,
verbosity.try_into()?,
false,
GenerateArtifacts::CodeOnly,
unstable_options.try_into()?,
)?;
assert!(res.dest_wasm.is_none(), "no dest_wasm should exist");
Ok("\nYour contract's code was built successfully.".to_string())
}
Command::GenerateMetadata {} => Err(anyhow::anyhow!(
"Command deprecated, use `cargo contract build` instead"
)),
Command::Test {} => Err(anyhow::anyhow!("Command unimplemented")),
#[cfg(feature = "extrinsics")]
Command::Deploy {
extrinsic_opts,
wasm_path,
} => {
let code_hash = cmd::execute_deploy(extrinsic_opts, wasm_path.as_ref())?;
Ok(format!("Code hash: {:?}", code_hash))
}
#[cfg(feature = "extrinsics")]
Command::Instantiate {
extrinsic_opts,
endowment,
code_hash,
gas_limit,
data,
} => {
let contract_account = cmd::execute_instantiate(
extrinsic_opts,
*endowment,
*gas_limit,
*code_hash,
data.clone(),
)?;
Ok(format!("Contract account: {:?}", contract_account))
}
}
}