From 51117ccbc0e77758118fffbd96c8975c506f6c54 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 10 Aug 2022 18:00:00 -0400 Subject: [PATCH 01/21] Add skeleton of `Verify` command --- crates/cargo-contract/src/cmd/mod.rs | 2 ++ crates/cargo-contract/src/cmd/verify.rs | 38 +++++++++++++++++++++++++ crates/cargo-contract/src/main.rs | 5 ++++ 3 files changed, 45 insertions(+) create mode 100644 crates/cargo-contract/src/cmd/verify.rs diff --git a/crates/cargo-contract/src/cmd/mod.rs b/crates/cargo-contract/src/cmd/mod.rs index 31a73208f..c1b2b7302 100644 --- a/crates/cargo-contract/src/cmd/mod.rs +++ b/crates/cargo-contract/src/cmd/mod.rs @@ -19,6 +19,7 @@ pub mod decode; pub mod metadata; pub mod new; pub mod test; +pub mod verify; pub(crate) use self::{ build::{ @@ -27,6 +28,7 @@ pub(crate) use self::{ }, decode::DecodeCommand, test::TestCommand, + verify::VerifyCommand, }; mod extrinsics; diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs new file mode 100644 index 000000000..727d3021a --- /dev/null +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -0,0 +1,38 @@ +// Copyright 2018-2022 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 . + +use crate::workspace::ManifestPath; + +use anyhow::Result; + +use std::path::PathBuf; + +#[derive(Debug, clap::Args)] +#[clap(name = "verify")] +pub struct VerifyCommand { + /// Path to the `Cargo.toml` of the contract to verify. + #[clap(long, parse(from_os_str))] + manifest_path: Option, + /// The reference Wasm contract (`*.contract`) that the workspace will be checked against. + contract_wasm: String, +} + +impl VerifyCommand { + pub fn run(&self) -> Result<()> { + let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + todo!() + } +} diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index afdbf2363..6d5806c1f 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -34,6 +34,7 @@ use self::{ InstantiateCommand, TestCommand, UploadCommand, + VerifyCommand, }, util::DEFAULT_KEY_COL_WIDTH, workspace::ManifestPath, @@ -504,6 +505,9 @@ enum Command { /// Decodes a contracts input or output data (supplied in hex-encoding) #[clap(name = "decode")] Decode(DecodeCommand), + /// Verifies that a given contract binary matches the build result of the specified workspace. + #[clap(name = "verify")] + Verify(VerifyCommand), } fn main() { @@ -570,6 +574,7 @@ fn exec(cmd: Command) -> Result<()> { .map_err(|err| map_extrinsic_err(err, call.is_json())) } Command::Decode(decode) => decode.run().map_err(format_err), + Command::Verify(verify) => verify.run(), } } From 13d29dc912861acf7d42ce472dfc9d6eb250fa95 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 10 Aug 2022 18:26:48 -0400 Subject: [PATCH 02/21] Read `BuildInfo` from contract metadata --- crates/cargo-contract/src/cmd/verify.rs | 31 +++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 727d3021a..7545e18a6 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -14,11 +14,19 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::workspace::ManifestPath; +use crate::{ + cmd::metadata::BuildInfo, + workspace::ManifestPath, +}; use anyhow::Result; +use contract_metadata::ContractMetadata; -use std::path::PathBuf; +use std::{ + fs::File, + io::prelude::Read, + path::PathBuf, +}; #[derive(Debug, clap::Args)] #[clap(name = "verify")] @@ -27,12 +35,27 @@ pub struct VerifyCommand { #[clap(long, parse(from_os_str))] manifest_path: Option, /// The reference Wasm contract (`*.contract`) that the workspace will be checked against. - contract_wasm: String, + contract: PathBuf, } impl VerifyCommand { pub fn run(&self) -> Result<()> { - let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let _manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + + // 1. Read the given metadata, and pull out the `BuildInfo` + let mut file = File::open(&self.contract)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let metadata: ContractMetadata = serde_json::from_str(&contents)?; + let build_info = metadata.source.build_info.as_ref().unwrap(); + let build_info: BuildInfo = + serde_json::from_value(build_info.clone().into()).unwrap(); + dbg!(&build_info); + + // 2. Call `cmd::Build` with the given `BuildInfo` + // + // 3. Read output file, compare with given contract_wasm todo!() } } From 85ccd7dd282ac01d5a075f8055fbc2bd4bb076fc Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 10 Aug 2022 18:39:10 -0400 Subject: [PATCH 03/21] Manually build contract using `BuildInfo` --- crates/cargo-contract/src/cmd/verify.rs | 27 ++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 7545e18a6..b87ac9ba0 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -15,8 +15,15 @@ // along with cargo-contract. If not, see . use crate::{ - cmd::metadata::BuildInfo, + cmd::{ + build::{ + execute, + ExecuteArgs, + }, + metadata::BuildInfo, + }, workspace::ManifestPath, + BuildArtifacts, }; use anyhow::Result; @@ -40,7 +47,7 @@ pub struct VerifyCommand { impl VerifyCommand { pub fn run(&self) -> Result<()> { - let _manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; // 1. Read the given metadata, and pull out the `BuildInfo` let mut file = File::open(&self.contract)?; @@ -54,7 +61,21 @@ impl VerifyCommand { dbg!(&build_info); // 2. Call `cmd::Build` with the given `BuildInfo` - // + let args = ExecuteArgs { + manifest_path, + verbosity: Default::default(), + build_mode: build_info.build_mode, + network: Default::default(), + build_artifact: BuildArtifacts::CodeOnly, + unstable_flags: Default::default(), + optimization_passes: build_info.wasm_opt_settings.optimization_passes, + keep_debug_symbols: false, /* TODO: Will either want to add this to BuildInfo or assume release (so no) */ + skip_linting: true, + output_type: Default::default(), + }; + + let _build_result = execute(args)?; + // 3. Read output file, compare with given contract_wasm todo!() } From 7de595c5d9d79db42a5141fe82042ca1aec1470c Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 11 Aug 2022 13:17:37 -0400 Subject: [PATCH 04/21] Check built Wasm file against that in the reference metadata --- crates/cargo-contract/src/cmd/verify.rs | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index b87ac9ba0..42a899e97 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -58,11 +58,10 @@ impl VerifyCommand { let build_info = metadata.source.build_info.as_ref().unwrap(); let build_info: BuildInfo = serde_json::from_value(build_info.clone().into()).unwrap(); - dbg!(&build_info); // 2. Call `cmd::Build` with the given `BuildInfo` let args = ExecuteArgs { - manifest_path, + manifest_path: manifest_path.clone(), verbosity: Default::default(), build_mode: build_info.build_mode, network: Default::default(), @@ -74,9 +73,41 @@ impl VerifyCommand { output_type: Default::default(), }; - let _build_result = execute(args)?; + let build_result = execute(args)?; // 3. Read output file, compare with given contract_wasm - todo!() + let reference_wasm = metadata.source.wasm.unwrap().to_string(); + + let built_wasm_path = build_result.dest_wasm.unwrap(); + let fs_wasm = std::fs::read(built_wasm_path)?; + let built_wasm = build_byte_str(&fs_wasm); + + if reference_wasm != built_wasm { + log::debug!( + "Expected Wasm Binary '{}'\n\nGot Wasm Binary `{}`", + &reference_wasm, + &built_wasm + ); + anyhow::bail!( + "Failed to verify the authenticity of `{}` contract againt the workspace found at {:?}.", + metadata.contract.name, + manifest_path.as_ref(), + ); + } + + log::info!("Succesfully verified `{}`!", &metadata.contract.name); + + Ok(()) + } +} + +fn build_byte_str(bytes: &[u8]) -> String { + use std::fmt::Write; + + let mut str = String::new(); + write!(str, "0x").expect("failed writing to string"); + for byte in bytes { + write!(str, "{:02x}", byte).expect("failed writing to string"); } + str } From e67d108a0651eab7a8ab7b1fa0f0c3d3eea1d53c Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 11 Aug 2022 13:30:11 -0400 Subject: [PATCH 05/21] Comopare `SourceWasm`'s instead of byte strings --- crates/cargo-contract/src/cmd/verify.rs | 21 +++++++-------------- crates/metadata/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 42a899e97..b45c8855f 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -27,7 +27,10 @@ use crate::{ }; use anyhow::Result; -use contract_metadata::ContractMetadata; +use contract_metadata::{ + ContractMetadata, + SourceWasm, +}; use std::{ fs::File, @@ -76,11 +79,11 @@ impl VerifyCommand { let build_result = execute(args)?; // 3. Read output file, compare with given contract_wasm - let reference_wasm = metadata.source.wasm.unwrap().to_string(); + let reference_wasm = metadata.source.wasm.unwrap(); let built_wasm_path = build_result.dest_wasm.unwrap(); let fs_wasm = std::fs::read(built_wasm_path)?; - let built_wasm = build_byte_str(&fs_wasm); + let built_wasm = SourceWasm::new(fs_wasm); if reference_wasm != built_wasm { log::debug!( @@ -88,6 +91,7 @@ impl VerifyCommand { &reference_wasm, &built_wasm ); + anyhow::bail!( "Failed to verify the authenticity of `{}` contract againt the workspace found at {:?}.", metadata.contract.name, @@ -100,14 +104,3 @@ impl VerifyCommand { Ok(()) } } - -fn build_byte_str(bytes: &[u8]) -> String { - use std::fmt::Write; - - let mut str = String::new(); - write!(str, "0x").expect("failed writing to string"); - for byte in bytes { - write!(str, "{:02x}", byte).expect("failed writing to string"); - } - str -} diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs index 4735dabaf..41786bbf2 100644 --- a/crates/metadata/src/lib.rs +++ b/crates/metadata/src/lib.rs @@ -166,7 +166,7 @@ impl Source { } /// The bytes of the compiled Wasm smart contract. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct SourceWasm( #[serde( serialize_with = "byte_str::serialize_as_byte_str", From b90f12639a009fe87dee6cb1078d9b6f5469bedb Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 15 Aug 2022 13:46:51 -0400 Subject: [PATCH 06/21] Check that current `nightly` matches that from contract --- crates/cargo-contract/src/cmd/verify.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index b45c8855f..4e92c1d3a 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -63,6 +63,23 @@ impl VerifyCommand { serde_json::from_value(build_info.clone().into()).unwrap(); // 2. Call `cmd::Build` with the given `BuildInfo` + let expected_rustc_version = build_info.rustc_version; + let rustc_version = rustc_version::version_meta()?; + let rustc_version = format!( + "{:?}-{}", + rustc_version.channel, + rustc_version.commit_date.expect("TODO") + ) + .to_lowercase(); + + anyhow::ensure!( + rustc_version == expected_rustc_version, + "You are trying to `verify` a contract using the `{rustc_version}` toolchain.\n\ + However, the original contract was built using `{expected_rustc_version}`. Please\n\ + install the correct toolchain (`rustup install {expected_rustc_version}`) and\n\ + re-run the `verify` command.", + ); + let args = ExecuteArgs { manifest_path: manifest_path.clone(), verbosity: Default::default(), From a350f4c5d57da0d264f1252ae901928108e4f407 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 18 Aug 2022 16:20:03 -0400 Subject: [PATCH 07/21] Switch from `env_logger` to `tracing` --- crates/cargo-contract/src/cmd/verify.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 4e92c1d3a..bf8c1e979 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -103,7 +103,7 @@ impl VerifyCommand { let built_wasm = SourceWasm::new(fs_wasm); if reference_wasm != built_wasm { - log::debug!( + tracing::debug!( "Expected Wasm Binary '{}'\n\nGot Wasm Binary `{}`", &reference_wasm, &built_wasm @@ -116,7 +116,7 @@ impl VerifyCommand { ); } - log::info!("Succesfully verified `{}`!", &metadata.contract.name); + tracing::info!("Succesfully verified `{}`!", &metadata.contract.name); Ok(()) } From e87c51a8639f056c7c5db18e125adca998be91ac Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 18 Aug 2022 16:21:23 -0400 Subject: [PATCH 08/21] Use helper function to get current Rust toolchain --- crates/cargo-contract/src/cmd/verify.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index bf8c1e979..f6c5f70d5 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -64,13 +64,7 @@ impl VerifyCommand { // 2. Call `cmd::Build` with the given `BuildInfo` let expected_rustc_version = build_info.rustc_version; - let rustc_version = rustc_version::version_meta()?; - let rustc_version = format!( - "{:?}-{}", - rustc_version.channel, - rustc_version.commit_date.expect("TODO") - ) - .to_lowercase(); + let rustc_version = crate::util::rustc_toolchain()?; anyhow::ensure!( rustc_version == expected_rustc_version, From 4317f550892051b036104215286e14778dba2460 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 18 Aug 2022 16:27:54 -0400 Subject: [PATCH 09/21] Clean up docs a little --- crates/cargo-contract/src/cmd/verify.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index f6c5f70d5..c90d61a36 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -38,6 +38,7 @@ use std::{ path::PathBuf, }; +/// Checks if a contract in the given workspace matches that of a reference contract. #[derive(Debug, clap::Args)] #[clap(name = "verify")] pub struct VerifyCommand { @@ -62,7 +63,7 @@ impl VerifyCommand { let build_info: BuildInfo = serde_json::from_value(build_info.clone().into()).unwrap(); - // 2. Call `cmd::Build` with the given `BuildInfo` + // 2. Call `cargo contract build` with the `BuildInfo` from the metadata. let expected_rustc_version = build_info.rustc_version; let rustc_version = crate::util::rustc_toolchain()?; @@ -89,7 +90,7 @@ impl VerifyCommand { let build_result = execute(args)?; - // 3. Read output file, compare with given contract_wasm + // 3. Grab the built Wasm contract and compare it with the Wasm from the metadata. let reference_wasm = metadata.source.wasm.unwrap(); let built_wasm_path = build_result.dest_wasm.unwrap(); From fed9c3ffa46469312ad93499001f506b40ba112d Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 19 Aug 2022 16:43:34 -0400 Subject: [PATCH 10/21] Bail out if `wasm-opt` versions don't match This should maybe be a warning instead of an error, but I'll need to experiment with this more. --- crates/cargo-contract/src/cmd/verify.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index c90d61a36..6ae1a4f7b 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -75,6 +75,22 @@ impl VerifyCommand { re-run the `verify` command.", ); + let expected_wasm_opt_version = build_info.wasm_opt_settings.version; + // TODO: Will either want to add this to BuildInfo or assume release (so no) + let keep_debug_symbols = false; + let handler = crate::wasm_opt::WasmOptHandler::new( + build_info.wasm_opt_settings.optimization_passes, + keep_debug_symbols, + )?; + let wasm_opt_version = handler.version(); + + anyhow::ensure!( + wasm_opt_version == expected_wasm_opt_version, + "You are trying to `verify` a contract using `wasm-opt` version {wasm_opt_version}`.\n\ + However, the original contract was built using `wasm-opt` version {expected_wasm_opt_version}`.\n\ + Please install the matching version and re-run the `verify` command.", + ); + let args = ExecuteArgs { manifest_path: manifest_path.clone(), verbosity: Default::default(), @@ -83,7 +99,7 @@ impl VerifyCommand { build_artifact: BuildArtifacts::CodeOnly, unstable_flags: Default::default(), optimization_passes: build_info.wasm_opt_settings.optimization_passes, - keep_debug_symbols: false, /* TODO: Will either want to add this to BuildInfo or assume release (so no) */ + keep_debug_symbols, skip_linting: true, output_type: Default::default(), }; From b74398730df990d044f039254f3a25db470070d6 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 29 Aug 2022 15:09:08 -0400 Subject: [PATCH 11/21] Use stable `rustc` toolchain --- crates/cargo-contract/src/cmd/verify.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 6ae1a4f7b..a04418b26 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -65,7 +65,8 @@ impl VerifyCommand { // 2. Call `cargo contract build` with the `BuildInfo` from the metadata. let expected_rustc_version = build_info.rustc_version; - let rustc_version = crate::util::rustc_toolchain()?; + let rustc_version = rustc_version::version() + .expect("`rustc` always has a version associated with it."); anyhow::ensure!( rustc_version == expected_rustc_version, From f6770dedf217997d52fcc45ed0cc946bc6ecf3a9 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 16:42:08 -0400 Subject: [PATCH 12/21] Use `keep_debug_symbols` from metadata --- crates/cargo-contract/src/cmd/verify.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index a04418b26..591a5e415 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -77,8 +77,7 @@ impl VerifyCommand { ); let expected_wasm_opt_version = build_info.wasm_opt_settings.version; - // TODO: Will either want to add this to BuildInfo or assume release (so no) - let keep_debug_symbols = false; + let keep_debug_symbols = build_info.wasm_opt_settings.keep_debug_symbols; let handler = crate::wasm_opt::WasmOptHandler::new( build_info.wasm_opt_settings.optimization_passes, keep_debug_symbols, From 2f3ca03a0d3ed6cd9b2f8e9ae1d773628962abf2 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 16:42:19 -0400 Subject: [PATCH 13/21] Make output more colourful --- crates/cargo-contract/src/cmd/verify.rs | 27 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 591a5e415..ce701eb4d 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -22,11 +22,14 @@ use crate::{ }, metadata::BuildInfo, }, + maybe_println, workspace::ManifestPath, BuildArtifacts, + Verbosity, + VerbosityFlags, }; - use anyhow::Result; +use colored::Colorize; use contract_metadata::{ ContractMetadata, SourceWasm, @@ -47,11 +50,15 @@ pub struct VerifyCommand { manifest_path: Option, /// The reference Wasm contract (`*.contract`) that the workspace will be checked against. contract: PathBuf, + /// + #[clap(flatten)] + verbosity: VerbosityFlags, } impl VerifyCommand { pub fn run(&self) -> Result<()> { let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let verbosity: Verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; // 1. Read the given metadata, and pull out the `BuildInfo` let mut file = File::open(&self.contract)?; @@ -93,7 +100,7 @@ impl VerifyCommand { let args = ExecuteArgs { manifest_path: manifest_path.clone(), - verbosity: Default::default(), + verbosity, build_mode: build_info.build_mode, network: Default::default(), build_artifact: BuildArtifacts::CodeOnly, @@ -120,14 +127,20 @@ impl VerifyCommand { &built_wasm ); - anyhow::bail!( - "Failed to verify the authenticity of `{}` contract againt the workspace found at {:?}.", - metadata.contract.name, - manifest_path.as_ref(), + anyhow::bail!(format!( + "\nFailed to verify the authenticity of {} contract againt the workspace \n\ + found at {}.", + format!("`{}`", metadata.contract.name).bright_white(), + format!("{:?}", manifest_path.as_ref()).bright_white()).bright_red() ); } - tracing::info!("Succesfully verified `{}`!", &metadata.contract.name); + maybe_println!( + verbosity, + " \n{} {}", + "Succesfully verified contract".bright_green().bold(), + format!("`{}`!", &metadata.contract.name).bold(), + ); Ok(()) } From e2c567565bb893e01396d820cafbd0e9bd963861 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 16:43:51 -0400 Subject: [PATCH 14/21] Add missing doc comment --- crates/cargo-contract/src/cmd/verify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index ce701eb4d..f358945bc 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -50,7 +50,7 @@ pub struct VerifyCommand { manifest_path: Option, /// The reference Wasm contract (`*.contract`) that the workspace will be checked against. contract: PathBuf, - /// + /// Denotes if output should be printed to stdout. #[clap(flatten)] verbosity: VerbosityFlags, } From 963387cad260401b290394cdabfcb5de2b63e9ff Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 17:23:01 -0400 Subject: [PATCH 15/21] Do a better job of error handling --- crates/cargo-contract/src/cmd/verify.rs | 64 +++++++++++++++++++------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index f358945bc..339804190 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -66,22 +66,36 @@ impl VerifyCommand { file.read_to_string(&mut contents)?; let metadata: ContractMetadata = serde_json::from_str(&contents)?; - let build_info = metadata.source.build_info.as_ref().unwrap(); - let build_info: BuildInfo = - serde_json::from_value(build_info.clone().into()).unwrap(); + let build_info = if let Some(info) = metadata.source.build_info { + info + } else { + anyhow::bail!( + "\nThe metadata does not contain any build information which can be used to \ + verify a contract." + .to_string() + .bright_yellow() + ) + }; + + let build_info: BuildInfo = serde_json::from_value(build_info.clone().into())?; + + tracing::debug!( + "Parsed the following build info from the metadata: {:?}", + &build_info, + ); // 2. Call `cargo contract build` with the `BuildInfo` from the metadata. let expected_rustc_version = build_info.rustc_version; let rustc_version = rustc_version::version() .expect("`rustc` always has a version associated with it."); - anyhow::ensure!( - rustc_version == expected_rustc_version, - "You are trying to `verify` a contract using the `{rustc_version}` toolchain.\n\ + let rustc_matches = rustc_version == expected_rustc_version; + let mismatched_rustc = format!( + "\nYou are trying to `verify` a contract using the `{rustc_version}` toolchain.\n\ However, the original contract was built using `{expected_rustc_version}`. Please\n\ install the correct toolchain (`rustup install {expected_rustc_version}`) and\n\ - re-run the `verify` command.", - ); + re-run the `verify` command.",); + anyhow::ensure!(rustc_matches, mismatched_rustc.bright_yellow()); let expected_wasm_opt_version = build_info.wasm_opt_settings.version; let keep_debug_symbols = build_info.wasm_opt_settings.keep_debug_symbols; @@ -91,12 +105,13 @@ impl VerifyCommand { )?; let wasm_opt_version = handler.version(); - anyhow::ensure!( - wasm_opt_version == expected_wasm_opt_version, - "You are trying to `verify` a contract using `wasm-opt` version {wasm_opt_version}`.\n\ - However, the original contract was built using `wasm-opt` version {expected_wasm_opt_version}`.\n\ + let wasm_opt_matches = wasm_opt_version == expected_wasm_opt_version; + let mismatched_wasm_opt = format!( + "\nYou are trying to `verify` a contract using `wasm-opt` version `{wasm_opt_version}`.\n\ + However, the original contract was built using `wasm-opt` version `{expected_wasm_opt_version}`.\n\ Please install the matching version and re-run the `verify` command.", ); + anyhow::ensure!(wasm_opt_matches, mismatched_wasm_opt.bright_yellow()); let args = ExecuteArgs { manifest_path: manifest_path.clone(), @@ -114,9 +129,30 @@ impl VerifyCommand { let build_result = execute(args)?; // 3. Grab the built Wasm contract and compare it with the Wasm from the metadata. - let reference_wasm = metadata.source.wasm.unwrap(); + let reference_wasm = if let Some(wasm) = metadata.source.wasm { + wasm + } else { + anyhow::bail!( + "\nThe metadata for the reference contract does not contain a Wasm binary,\n\ + therefore we are unable to verify the contract." + .to_string() + .bright_yellow() + ) + }; + + let built_wasm_path = if let Some(wasm) = build_result.dest_wasm { + wasm + } else { + // Since we're building the contract ourselves this should always be populated, + // but we'll bail out here just in case. + anyhow::bail!( + "\nThe metadata for the workspace contract does not contain a Wasm binary,\n\ + therefore we are unable to verify the contract." + .to_string() + .bright_yellow() + ) + }; - let built_wasm_path = build_result.dest_wasm.unwrap(); let fs_wasm = std::fs::read(built_wasm_path)?; let built_wasm = SourceWasm::new(fs_wasm); From e7b957783fc283375273ed8f4e735b2247795834 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 17:36:01 -0400 Subject: [PATCH 16/21] Add more error handling improvements --- crates/cargo-contract/src/cmd/verify.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 339804190..45fc76c08 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -28,7 +28,10 @@ use crate::{ Verbosity, VerbosityFlags, }; -use anyhow::Result; +use anyhow::{ + Context, + Result, +}; use colored::Colorize; use contract_metadata::{ ContractMetadata, @@ -37,7 +40,6 @@ use contract_metadata::{ use std::{ fs::File, - io::prelude::Read, path::PathBuf, }; @@ -61,11 +63,13 @@ impl VerifyCommand { let verbosity: Verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; // 1. Read the given metadata, and pull out the `BuildInfo` - let mut file = File::open(&self.contract)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; + let path = &self.contract; + let file = File::open(path) + .context(format!("Failed to open contract bundle {}", path.display()))?; - let metadata: ContractMetadata = serde_json::from_str(&contents)?; + let metadata: ContractMetadata = serde_json::from_reader(&file).context( + format!("Failed to deserialize contract bundle {}", path.display()), + )?; let build_info = if let Some(info) = metadata.source.build_info { info } else { @@ -77,7 +81,11 @@ impl VerifyCommand { ) }; - let build_info: BuildInfo = serde_json::from_value(build_info.clone().into())?; + let build_info: BuildInfo = serde_json::from_value(build_info.clone().into()) + .context(format!( + "Failed to deserialize the build info from {}", + path.display() + ))?; tracing::debug!( "Parsed the following build info from the metadata: {:?}", From a64b631d919a24d03c936e01ee0c780aa4d0b955 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 17:44:29 -0400 Subject: [PATCH 17/21] Add `CHANGELOG` entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 126b48304..121d82466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add Rust specific build info to metadata - [#680](https://github.com/paritytech/cargo-contract/pull/680) +- Add `verify` command - [#696](https://github.com/paritytech/cargo-contract/pull/696) ### Changed - Removed requirement to install binaryen. The `wasm-opt` tool is now compiled into `cargo-contract`. From d6db73fab17ac0d8b599330ff02f0a7422ee3e00 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 29 Sep 2022 17:46:04 -0400 Subject: [PATCH 18/21] Appease Clippy --- crates/cargo-contract/src/cmd/verify.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 45fc76c08..a92e35e96 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -81,8 +81,8 @@ impl VerifyCommand { ) }; - let build_info: BuildInfo = serde_json::from_value(build_info.clone().into()) - .context(format!( + let build_info: BuildInfo = + serde_json::from_value(build_info.into()).context(format!( "Failed to deserialize the build info from {}", path.display() ))?; From 80233c43d5cfd61cd64fb4c2dee263d8e51ca805 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 20 Oct 2022 15:22:02 -0400 Subject: [PATCH 19/21] Use updated Rust toolchain build info --- crates/cargo-contract/src/cmd/verify.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index a92e35e96..773769e6b 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -93,15 +93,15 @@ impl VerifyCommand { ); // 2. Call `cargo contract build` with the `BuildInfo` from the metadata. - let expected_rustc_version = build_info.rustc_version; - let rustc_version = rustc_version::version() + let expected_rust_toolchain = build_info.rust_toolchain; + let rust_toolchain = crate::util::rust_toolchain() .expect("`rustc` always has a version associated with it."); - let rustc_matches = rustc_version == expected_rustc_version; + let rustc_matches = rust_toolchain == expected_rust_toolchain; let mismatched_rustc = format!( - "\nYou are trying to `verify` a contract using the `{rustc_version}` toolchain.\n\ - However, the original contract was built using `{expected_rustc_version}`. Please\n\ - install the correct toolchain (`rustup install {expected_rustc_version}`) and\n\ + "\nYou are trying to `verify` a contract using the `{rust_toolchain}` toolchain.\n\ + However, the original contract was built using `{expected_rust_toolchain}`. Please\n\ + install the correct toolchain (`rustup install {expected_rust_toolchain}`) and\n\ re-run the `verify` command.",); anyhow::ensure!(rustc_matches, mismatched_rustc.bright_yellow()); From 98cc7fbb057166e7743693fc96c0591369508571 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 20 Oct 2022 15:48:29 -0400 Subject: [PATCH 20/21] Apply fixes for `clap` `v4.0` --- crates/cargo-contract/src/cmd/verify.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 773769e6b..7826f9f84 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -48,7 +48,7 @@ use std::{ #[clap(name = "verify")] pub struct VerifyCommand { /// Path to the `Cargo.toml` of the contract to verify. - #[clap(long, parse(from_os_str))] + #[clap(long, value_parser)] manifest_path: Option, /// The reference Wasm contract (`*.contract`) that the workspace will be checked against. contract: PathBuf, From b2d10c26e3bed83bbbf26cd7eadbfe7d5bfa18a2 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 20 Oct 2022 15:48:51 -0400 Subject: [PATCH 21/21] Compare `cargo-contract` versions instead of `wasm-opt` versions Since releases of `cargo-contract` are now bundled with a `wasm-opt` library we can assume that equal versions of `cargo-contract` contain equal versions of `wasm-opt`. --- crates/cargo-contract/src/cmd/build/mod.rs | 2 +- crates/cargo-contract/src/cmd/verify.rs | 37 ++++++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/crates/cargo-contract/src/cmd/build/mod.rs b/crates/cargo-contract/src/cmd/build/mod.rs index b3fd8f429..ce51b950f 100644 --- a/crates/cargo-contract/src/cmd/build/mod.rs +++ b/crates/cargo-contract/src/cmd/build/mod.rs @@ -68,7 +68,7 @@ use std::{ const MAX_MEMORY_PAGES: u32 = 16; /// Version of the currently executing `cargo-contract` binary. -const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Arguments to use when executing `build` or `check` commands. #[derive(Default)] diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index 7826f9f84..d0f262892 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -19,6 +19,7 @@ use crate::{ build::{ execute, ExecuteArgs, + VERSION, }, metadata::BuildInfo, }, @@ -92,7 +93,7 @@ impl VerifyCommand { &build_info, ); - // 2. Call `cargo contract build` with the `BuildInfo` from the metadata. + // 2. Check that the build info from the metadata matches our current setup. let expected_rust_toolchain = build_info.rust_toolchain; let rust_toolchain = crate::util::rust_toolchain() .expect("`rustc` always has a version associated with it."); @@ -105,22 +106,26 @@ impl VerifyCommand { re-run the `verify` command.",); anyhow::ensure!(rustc_matches, mismatched_rustc.bright_yellow()); - let expected_wasm_opt_version = build_info.wasm_opt_settings.version; - let keep_debug_symbols = build_info.wasm_opt_settings.keep_debug_symbols; - let handler = crate::wasm_opt::WasmOptHandler::new( - build_info.wasm_opt_settings.optimization_passes, - keep_debug_symbols, - )?; - let wasm_opt_version = handler.version(); - - let wasm_opt_matches = wasm_opt_version == expected_wasm_opt_version; - let mismatched_wasm_opt = format!( - "\nYou are trying to `verify` a contract using `wasm-opt` version `{wasm_opt_version}`.\n\ - However, the original contract was built using `wasm-opt` version `{expected_wasm_opt_version}`.\n\ + let expected_cargo_contract_version = build_info.cargo_contract_version; + let cargo_contract_version = semver::Version::parse(VERSION)?; + + // Note, assuming both versions of `cargo-contract` were installed with the same lockfile + // (e.g `--locked`) then the versions of `wasm-opt` should also match. + let cargo_contract_matches = + cargo_contract_version == expected_cargo_contract_version; + let mismatched_cargo_contract = format!( + "\nYou are trying to `verify` a contract using `cargo-contract` version \ + `{cargo_contract_version}`.\n\ + However, the original contract was built using `cargo-contract` version \ + `{expected_cargo_contract_version}`.\n\ Please install the matching version and re-run the `verify` command.", ); - anyhow::ensure!(wasm_opt_matches, mismatched_wasm_opt.bright_yellow()); + anyhow::ensure!( + cargo_contract_matches, + mismatched_cargo_contract.bright_yellow() + ); + // 3. Call `cargo contract build` with the `BuildInfo` from the metadata. let args = ExecuteArgs { manifest_path: manifest_path.clone(), verbosity, @@ -129,14 +134,14 @@ impl VerifyCommand { build_artifact: BuildArtifacts::CodeOnly, unstable_flags: Default::default(), optimization_passes: build_info.wasm_opt_settings.optimization_passes, - keep_debug_symbols, + keep_debug_symbols: build_info.wasm_opt_settings.keep_debug_symbols, skip_linting: true, output_type: Default::default(), }; let build_result = execute(args)?; - // 3. Grab the built Wasm contract and compare it with the Wasm from the metadata. + // 4. Grab the built Wasm contract and compare it with the Wasm from the metadata. let reference_wasm = if let Some(wasm) = metadata.source.wasm { wasm } else {