// Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // // cargo-contract is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // cargo-contract is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . mod cmd; mod crate_metadata; mod util; mod workspace; #[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 { /// Utilities to develop Wasm smart contracts. #[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); #[cfg(feature = "extrinsics")] impl std::str::FromStr for HexData { type Err = hex::FromHexError; fn from_str(input: &str) -> std::result::Result { hex::decode(input).map(HexData) } } /// Arguments required for creating and sending an extrinsic to a substrate node #[cfg(feature = "extrinsics")] #[derive(Debug, StructOpt)] pub(crate) struct ExtrinsicOpts { /// Websockets url of a substrate node #[structopt( name = "url", long, parse(try_from_str), default_value = "ws://localhost:9944" )] url: url::Url, /// Secret key URI for the account deploying the contract. #[structopt(name = "suri", long, short)] suri: String, /// Password for the secret key #[structopt(name = "password", long, short)] password: Option, } #[cfg(feature = "extrinsics")] impl ExtrinsicOpts { pub fn signer(&self) -> Result> { 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 { type Error = Error; fn try_from(value: &VerbosityFlags) -> Result { 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 { /// Use the original manifest (Cargo.toml), do not modify for build optimizations #[structopt(long = "unstable-options", short = "Z", number_of_values = 1)] options: Vec, } #[derive(Clone, Default)] struct UnstableFlags { original_manifest: bool, } impl TryFrom<&UnstableOptions> for UnstableFlags { type Error = Error; fn try_from(value: &UnstableOptions) -> Result { let valid_flags = ["original-manifest"]; let invalid_flags = value .options .iter() .filter(|o| !valid_flags.contains(&o.as_str())) .collect::>(); if !invalid_flags.is_empty() { anyhow::bail!("Unknown unstable-options {:?}", invalid_flags) } Ok(UnstableFlags { original_manifest: value.options.contains(&"original-manifest".to_owned()), }) } } #[derive(Debug, StructOpt)] enum Command { /// Setup and create a new smart contract project #[structopt(name = "new")] New { /// The name of the newly created smart contract name: String, /// The optional target directory for the contract project #[structopt(short, long, parse(from_os_str))] target_dir: Option, }, /// Compiles the smart contract #[structopt(name = "build")] Build { #[structopt(flatten)] verbosity: VerbosityFlags, #[structopt(flatten)] unstable_options: UnstableOptions, }, /// Generate contract metadata artifacts #[structopt(name = "generate-metadata")] GenerateMetadata { #[structopt(flatten)] verbosity: VerbosityFlags, #[structopt(flatten)] unstable_options: UnstableOptions, }, /// Test the smart contract off-chain #[structopt(name = "test")] Test {}, /// Upload the smart contract code to the chain #[cfg(feature = "extrinsics")] #[structopt(name = "deploy")] Deploy { #[structopt(flatten)] extrinsic_opts: ExtrinsicOpts, /// Path to wasm contract code, defaults to ./target/-pruned.wasm #[structopt(parse(from_os_str))] wasm_path: Option, }, /// Instantiate a deployed smart contract #[cfg(feature = "extrinsics")] #[structopt(name = "instantiate")] Instantiate { #[structopt(flatten)] extrinsic_opts: ExtrinsicOpts, /// Transfers an initial balance to the instantiated contract #[structopt(name = "endowment", long, default_value = "0")] endowment: u128, /// Maximum amount of gas to be used for this command #[structopt(name = "gas", long, default_value = "500000000")] gas_limit: u64, /// The hash of the smart contract code already uploaded to the chain #[structopt(long, parse(try_from_str = parse_code_hash))] code_hash: H256, /// Hex encoded data to call a contract constructor #[structopt(long)] data: HexData, }, } #[cfg(feature = "extrinsics")] fn parse_code_hash(input: &str) -> Result { 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() ), } } fn exec(cmd: Command) -> Result { match &cmd { Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()), Command::Build { verbosity, unstable_options, } => { let manifest_path = Default::default(); let dest_wasm = cmd::build::execute( &manifest_path, verbosity.try_into()?, unstable_options.try_into()?, )?; Ok(format!( "\nYour contract is ready. You can find it here:\n{}", dest_wasm.display().to_string().bold() )) } Command::GenerateMetadata { verbosity, unstable_options, } => { let metadata_file = cmd::metadata::execute( Default::default(), verbosity.try_into()?, unstable_options.try_into()?, )?; Ok(format!( "Your metadata file is ready.\nYou can find it here:\n{}", metadata_file.display() )) } 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)) } } }