Newer
Older
// Copyright 2018-2021 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 <http://www.gnu.org/licenses/>.
Andrew Jones
committed
mod util;
mod validate_wasm;
Andrew Jones
committed
mod workspace;
use self::workspace::ManifestPath;
use crate::cmd::{BuildCommand, CheckCommand};
#[cfg(feature = "extrinsics")]
use sp_core::{crypto::Pair, sr25519, H256};
use std::{convert::TryFrom, path::PathBuf};
#[cfg(feature = "extrinsics")]
use subxt::PairSigner;
Andrew Jones
committed
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<u8>);
#[cfg(feature = "extrinsics")]
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
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<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))
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#[derive(Clone, Debug, StructOpt)]
pub struct OptimizationFlags {
/// number of optimization passes, passed as an argument to wasm-opt
///
/// - `0`: execute no optimization passes
///
/// - `1`: execute 1 optimization pass (quick & useful opts, useful for iteration builds)
///
/// - `2`, execute 2 optimization passes (most opts, generally gets most perf)
///
/// - `3`, execute 3 optimization passes (spends potentially a lot of time optimizing)
///
/// - `4`, execute 4 optimization passes (also flatten the IR, which can take a lot more time and memory
/// but is useful on more nested / complex / less-optimized input)
///
/// - `s`, execute default optimization passes, focusing on code size
///
/// - `z`, execute default optimization passes, super-focusing on code size
///
/// -
#[structopt(long = "optimization-passes", default_value = "3")]
optimization_passes: String,
}
#[derive(Clone, Copy)]
pub enum OptimizationPasses {
Zero,
One,
Two,
Three,
Four,
S,
Z,
}
impl TryFrom<&OptimizationFlags> for OptimizationPasses {
type Error = Error;
fn try_from(value: &OptimizationFlags) -> Result<Self, Self::Error> {
match value.optimization_passes.to_lowercase().as_str() {
"0" => Ok(OptimizationPasses::Zero),
"1" => Ok(OptimizationPasses::One),
"2" => Ok(OptimizationPasses::Two),
"3" => Ok(OptimizationPasses::Three),
"4" => Ok(OptimizationPasses::Four),
"s" => Ok(OptimizationPasses::S),
"z" => Ok(OptimizationPasses::Z),
_ => anyhow::bail!(
"Unknown optimization passes option {}",
value.optimization_passes
),
}
}
}
impl OptimizationPasses {
/// Returns the string representation of `OptimizationPasses`
pub(crate) fn to_str(&self) -> &str {
match self {
OptimizationPasses::Zero => "0",
OptimizationPasses::One => "1",
OptimizationPasses::Two => "2",
OptimizationPasses::Three => "3",
OptimizationPasses::Four => "4",
OptimizationPasses::S => "s",
OptimizationPasses::Z => "z",
}
}
/// Returns the number of optimization passes to do
#[cfg(feature = "binaryen-as-dependency")]
pub(crate) fn to_passes(&self) -> u32 {
match self {
OptimizationPasses::Zero => 0,
OptimizationPasses::One => 1,
OptimizationPasses::Two => 2,
OptimizationPasses::Three => 3,
OptimizationPasses::Four => 4,
_ => 3, // Default to three for shrink settings
}
}
/// Returns amount of shrinkage to do
#[cfg(feature = "binaryen-as-dependency")]
pub(crate) fn to_shrink(&self) -> u32 {
match self {
OptimizationPasses::Zero => 0,
OptimizationPasses::S => 1,
OptimizationPasses::Z => 2,
_ => 1,
}
}
}
#[derive(Clone, Debug, StructOpt)]
pub struct VerbosityFlags {
Andrew Jones
committed
#[structopt(long)]
quiet: bool,
Andrew Jones
committed
#[structopt(long)]
verbose: bool,
}
/// Denotes if output should be printed to stdout.
pub enum Verbosity {
/// Use default output
Default,
/// No output printed to stdout
Andrew Jones
committed
Quiet,
Andrew Jones
committed
Verbose,
}
impl Verbosity {
/// Returns `true` if output should be printed (i.e. verbose output is set).
pub(crate) fn is_verbose(&self) -> bool {
match self {
Verbosity::Quiet => false,
Verbosity::Default | Verbosity::Verbose => true,
}
}
}
impl TryFrom<&VerbosityFlags> for Verbosity {
Andrew Jones
committed
type Error = Error;
fn try_from(value: &VerbosityFlags) -> Result<Self, Self::Error> {
match (value.quiet, value.verbose) {
(false, false) => Ok(Verbosity::Default),
(true, false) => Ok(Verbosity::Quiet),
(false, true) => Ok(Verbosity::Verbose),
Andrew Jones
committed
(true, true) => anyhow::bail!("Cannot pass both --quiet and --verbose flags"),
}
}
}
#[derive(Clone, 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<String>,
}
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()),
})
}
}
Michael Müller
committed
/// Describes which artifacts to generate
#[derive(Copy, Clone, Eq, PartialEq, Debug, StructOpt)]
#[structopt(name = "build-artifacts")]
Michael Müller
committed
/// Generate the Wasm, the metadata and a bundled `<name>.contract` file
#[structopt(name = "all")]
All,
/// Only the Wasm is created, generation of metadata and a bundled `<name>.contract` file is skipped
#[structopt(name = "code-only")]
CodeOnly,
Michael Müller
committed
}
Michael Müller
committed
/// Returns the number of steps required to complete a build artifact.
/// Used as output on the cli.
pub fn steps(&self) -> usize {
match self {
BuildArtifacts::All => 5,
BuildArtifacts::CodeOnly => 3,
BuildArtifacts::CheckOnly => 2,
}
}
}
impl std::str::FromStr for BuildArtifacts {
type Err = String;
fn from_str(artifact: &str) -> Result<Self, Self::Err> {
match artifact {
"all" => Ok(BuildArtifacts::All),
"code-only" => Ok(BuildArtifacts::CodeOnly),
_ => Err("Could not parse build artifact".to_string()),
Michael Müller
committed
}
}
}
/// Result of the metadata generation process.
pub struct BuildResult {
/// Path to the resulting metadata file.
pub dest_metadata: Option<PathBuf>,
/// Path to the resulting Wasm file.
pub dest_wasm: Option<PathBuf>,
/// Path to the bundled file.
pub dest_bundle: Option<PathBuf>,
/// Path to the directory where output files are written to.
pub target_directory: PathBuf,
/// If existent the result of the optimization.
pub optimization_result: Option<OptimizationResult>,
/// Which build artifacts were generated.
pub build_artifact: BuildArtifacts,
/// The verbosity flags.
pub verbosity: Verbosity,
}
/// Result of the optimization process.
pub struct OptimizationResult {
/// The original Wasm size.
pub original_size: f64,
/// The Wasm size after optimizations have been applied.
pub optimized_size: f64,
}
Michael Müller
committed
impl BuildResult {
pub fn display(&self) -> String {
let optimization = self.display_optimization();
Michael Müller
committed
let size_diff = format!(
"\nOriginal wasm size: {}, Optimized: {}\n\n",
format!("{:.1}K", optimization.0).bold(),
format!("{:.1}K", optimization.1).bold(),
);
Michael Müller
committed
debug_assert!(
optimization.1 > 0.0,
"optimized file size must be greater 0"
);
Michael Müller
committed
if self.build_artifact == BuildArtifacts::CodeOnly {
Michael Müller
committed
let out = format!(
"{}Your contract's code is ready. You can find it here:\n{}",
size_diff,
Michael Müller
committed
.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,
self.target_directory.display().to_string().bold(),
Michael Müller
committed
);
if let Some(dest_bundle) = self.dest_bundle.as_ref() {
Michael Müller
committed
let bundle = format!(
" - {} (code + metadata)\n",
util::base_name(&dest_bundle).bold()
Michael Müller
committed
);
out.push_str(&bundle);
}
if let Some(dest_wasm) = self.dest_wasm.as_ref() {
Michael Müller
committed
let wasm = format!(
" - {} (the contract's code)\n",
util::base_name(&dest_wasm).bold()
Michael Müller
committed
);
out.push_str(&wasm);
}
if let Some(dest_metadata) = self.dest_metadata.as_ref() {
Michael Müller
committed
let metadata = format!(
" - {} (the contract's metadata)",
util::base_name(&dest_metadata).bold()
Michael Müller
committed
);
out.push_str(&metadata);
}
out
}
/// Returns a tuple of `(original_size, optimized_size)`.
///
/// Panics if no optimization result is available.
fn display_optimization(&self) -> (f64, f64) {
let optimization = self
Michael Müller
committed
.optimization_result
.as_ref()
.expect("optimization result must exist");
(optimization.original_size, optimization.optimized_size)
}
}
#[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<PathBuf>,
Michael Müller
committed
/// Compiles the contract, generates metadata, bundles both together in a `<name>.contract` file
#[structopt(name = "build")]
Michael Müller
committed
/// Command has been deprecated, use `cargo contract build` instead
#[structopt(name = "generate-metadata")]
Michael Müller
committed
GenerateMetadata {},
/// Check that the code builds as Wasm; does not output any `<name>.contract` artifact to the `target/` directory
Michael Müller
committed
#[structopt(name = "check")]
/// 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/ink/<name>.wasm`
/// 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<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))
Andrew Jones
committed
fn main() {
let Opts::Contract(args) = Opts::from_args();
Andrew Jones
committed
match exec(args.cmd) {
Ok(maybe_msg) => {
if let Some(msg) = maybe_msg {
println!("\t{}", msg)
}
}
Err(err) => {
eprintln!(
"{} {}",
"ERROR:".bright_red().bold(),
format!("{:?}", err).bright_red()
);
std::process::exit(1);
}
Andrew Jones
committed
}
}
fn exec(cmd: Command) -> Result<Option<String>> {
Andrew Jones
committed
match &cmd {
Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()),
Command::Build(build) => {
let result = build.exec()?;
if result.verbosity.is_verbose() {
Ok(Some(result.display()))
} else {
Ok(None)
}
Command::Check(check) => {
let res = check.exec()?;
assert!(
res.dest_wasm.is_none(),
"no dest_wasm must be on the generation result"
);
if res.verbosity.is_verbose() {
Ok(Some(
"\nYour contract's code was built successfully.".to_string(),
))
} else {
Ok(None)
}
Michael Müller
committed
Command::GenerateMetadata {} => Err(anyhow::anyhow!(
"Command deprecated, use `cargo contract build` instead"
)),
Command::Test {} => Err(anyhow::anyhow!("Command unimplemented")),
} => {
let code_hash = cmd::execute_deploy(extrinsic_opts, wasm_path.as_ref())?;
Ok(Some(format!("Code hash: {:?}", code_hash)))
#[cfg(feature = "extrinsics")]
Command::Instantiate {
extrinsic_opts,
endowment,
code_hash,
let contract_account = cmd::execute_instantiate(
extrinsic_opts,
*endowment,
*gas_limit,
*code_hash,
data.clone(),
)?;
Ok(Some(format!("Contract account: {:?}", contract_account)))