Newer
Older
// 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 <http://www.gnu.org/licenses/>.
Andrew Jones
committed
mod util;
mod workspace;
use self::workspace::ManifestPath;
#[cfg(feature = "extrinsics")]
use sp_core::{crypto::Pair, sr25519, H256};
Andrew Jones
committed
use std::{
convert::{TryFrom, TryInto},
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))
Andrew Jones
committed
#[derive(Debug, StructOpt)]
struct VerbosityFlags {
#[structopt(long)]
quiet: bool,
#[structopt(long)]
verbose: bool,
}
Andrew Jones
committed
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 {
/// 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
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/// Describes which artifacts to generate
#[derive(Copy, Clone, Eq, PartialEq, Debug, StructOpt)]
#[structopt(name = "build-artifacts")]
pub enum GenerateArtifacts {
/// 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,
}
impl GenerateArtifacts {
/// Returns the number of steps required to complete a build artifact.
/// Used as output on the cli.
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()),
}
}
}
/// Result of the metadata generation process.
pub struct GenerationResult {
/// 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>,
}
/// 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,
}
impl GenerationResult {
/// Returns the base name of the path.
pub fn display(path: &PathBuf) -> &str {
path.file_name()
.expect("file name must exist")
.to_str()
.expect("must be valid utf-8")
}
/// Returns a tuple of `(original_size, optimized_size)`.
///
/// Panics if no optimization result is available.
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 {
/// 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")]
Andrew Jones
committed
Build {
/// Path to the Cargo.toml of the contract to build
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
Michael Müller
committed
/// Which build artifacts to generate.
///
/// - `all`: Generate the Wasm, the metadata and a bundled `<name>.contract` file.
///
/// - `code-only`: Only the Wasm is created, generation of metadata and a bundled
/// `<name>.contract` file is skipped.
#[structopt(
long = "generate",
default_value = "all",
value_name = "all | code-only",
Michael Müller
committed
verbatim_doc_comment
)]
build_artifact: GenerateArtifacts,
Andrew Jones
committed
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
Andrew Jones
committed
},
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 build artifact to the top level `target/` directory
#[structopt(name = "check")]
Check {
/// Path to the Cargo.toml of the contract to build
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
Andrew Jones
committed
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
Andrew Jones
committed
},
/// 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,
Michael Müller
committed
/// Path to wasm contract code, defaults to `./target/<name>-pruned.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(msg) => println!("\t{}", msg),
Andrew Jones
committed
Err(err) => eprintln!(
"{} {}",
"ERROR:".bright_red().bold(),
format!("{:?}", err).bright_red()
),
Andrew Jones
committed
}
}
fn exec(cmd: Command) -> Result<String> {
Andrew Jones
committed
match &cmd {
Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()),
Command::Build {
manifest_path,
Michael Müller
committed
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()?,
)?;
Michael Müller
committed
Ok(build_artifact.display(&result))
Michael Müller
committed
Command::Check {
manifest_path,
verbosity,
unstable_options,
let manifest_path = ManifestPath::try_from(manifest_path.as_ref())?;
Michael Müller
committed
let res = cmd::build::execute(
&manifest_path,
Michael Müller
committed
false,
GenerateArtifacts::CodeOnly,
unstable_options.try_into()?,
)?;
Michael Müller
committed
assert!(res.dest_wasm.is_none(), "no dest_wasm should exist");
Ok("\nYour contract's code was built successfully.".to_string())
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(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(format!("Contract account: {:?}", contract_account))