diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d564e894..839cbface 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Verify raw Wasm in cargo contract verify - [#1551](https://github.com/paritytech/cargo-contract/pull/1551) + ## [4.0.2] ### Fixed diff --git a/crates/cargo-contract/src/cmd/verify.rs b/crates/cargo-contract/src/cmd/verify.rs index f60bbdd8d..88c379071 100644 --- a/crates/cargo-contract/src/cmd/verify.rs +++ b/crates/cargo-contract/src/cmd/verify.rs @@ -20,6 +20,7 @@ use anyhow::{ }; use colored::Colorize; use contract_build::{ + code_hash, execute, verbose_eprintln, BuildArtifacts, @@ -31,7 +32,10 @@ use contract_build::{ Verbosity, VerbosityFlags, }; -use contract_metadata::ContractMetadata; +use contract_metadata::{ + CodeHash, + ContractMetadata, +}; use std::{ fs::File, @@ -49,7 +53,12 @@ pub struct VerifyCommand { manifest_path: Option, /// The reference Wasm contract (`*.contract`) that the workspace will be checked /// against. - contract: PathBuf, + #[clap(long)] + contract: Option, + /// The reference Wasm contract binary (`*.wasm`) that the workspace will be checked + /// against. + #[clap(long, conflicts_with = "contract")] + wasm: Option, /// Denotes if output should be printed to stdout. #[clap(flatten)] verbosity: VerbosityFlags, @@ -62,9 +71,90 @@ 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)?; + if let Some(path) = &self.contract { + self.verify_contract(manifest_path, verbosity, path) + } else if let Some(path) = &self.wasm { + self.verify_wasm(manifest_path, verbosity, path) + } else { + anyhow::bail!("Either --wasm or --contract must be specified") + } + } + + /// Verify `.wasm` binary. + fn verify_wasm( + &self, + manifest_path: ManifestPath, + verbosity: Verbosity, + path: &PathBuf, + ) -> Result { + // 1. Read code hash binary from the path. + let ref_buffer = std::fs::read(path) + .context(format!("Failed to read contract binary {}", path.display()))?; + + let reference_code_hash = CodeHash(code_hash(&ref_buffer)); + + // 2. Call `cargo contract build` in the release mode. + let args = ExecuteArgs { + manifest_path: manifest_path.clone(), + verbosity, + optimization_passes: Some(contract_build::OptimizationPasses::Z), + build_mode: BuildMode::Release, + build_artifact: BuildArtifacts::CodeOnly, + extra_lints: false, + ..Default::default() + }; + + let build_result = execute(args)?; + + // 4. Grab the code hash from the built contract and compare it with the reference + // one. + let built_wasm_path = if let Some(m) = build_result.dest_wasm { + m + } 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 workspace contract does not contain a Wasm binary,\n\ + therefore we are unable to verify the contract." + .to_string() + .bright_yellow()) + }; + let target_buffer = std::fs::read(&built_wasm_path).context(format!( + "Failed to read contract binary {}", + built_wasm_path.display() + ))?; + + let output_code_hash = CodeHash(code_hash(&target_buffer)); + + if output_code_hash != reference_code_hash { + anyhow::bail!(format!( + "\nFailed to verify the authenticity of wasm binary at {} against the workspace \n\ + found at {}.\n Expected {}, found {}", + format!("`{}`", path.display()).bright_white(), + format!("`{}`", built_wasm_path.display()).bright_white(), + format!("{}", reference_code_hash).bright_white(), + format!("{}", output_code_hash).bright_white()) + ); + } + + Ok(VerificationResult { + is_verified: true, + image: None, + contract: built_wasm_path.display().to_string(), + reference_contract: path.display().to_string(), + output_json: self.output_json, + verbosity, + }) + } + + /// Verify the `.contract` bundle. + fn verify_contract( + &self, + manifest_path: ManifestPath, + verbosity: Verbosity, + path: &PathBuf, + ) -> Result { // 1. Read the given metadata, and pull out the `BuildInfo` - let path = &self.contract; let file = File::open(path) .context(format!("Failed to open contract bundle {}", path.display()))?; @@ -166,7 +256,7 @@ impl VerifyCommand { ) }; - let target_bundle = built_contract_path.dest_bundle; + let target_bundle = &built_contract_path.dest_bundle; let file = File::open(target_bundle.clone()).context(format!( "Failed to open contract bundle {}", @@ -187,7 +277,6 @@ impl VerifyCommand { &reference_code_hash, &target_code_hash ); - anyhow::bail!(format!( "\nFailed to verify the authenticity of {} contract against the workspace \n\ found at {}.", diff --git a/crates/cargo-contract/tests/verify.rs b/crates/cargo-contract/tests/verify.rs index 52b096e52..f34f4db6b 100644 --- a/crates/cargo-contract/tests/verify.rs +++ b/crates/cargo-contract/tests/verify.rs @@ -23,8 +23,9 @@ fn cargo_contract>(path: P) -> assert_cmd::Command { cmd } -/// Compile the reference contract and return a byte array of its bundle. -fn compile_reference_contract() -> Vec { +/// Compile the reference contract and return a byte array of its bundle and raw wasm +/// binary. +fn compile_reference_contract() -> (Vec, Vec) { let contract = r#" #![cfg_attr(not(feature = "std"), no_std, no_main)] @@ -48,7 +49,7 @@ fn compile_reference_contract() -> Vec { #[ink(message)] pub fn inc(&mut self, by: i32) { - self.value.saturating_add(by); + self.value = self.value.saturating_add(by); } #[ink(message, selector = 0xCACACACA)] @@ -82,9 +83,14 @@ fn compile_reference_contract() -> Vec { .success(); let bundle_path = project_dir.join("target/ink/incrementer.contract"); + let bundle = std::fs::read(bundle_path) + .expect("Failed to read the content of the contract bundle!"); - std::fs::read(bundle_path) - .expect("Failed to read the content of the contract bundle!") + let wasm_path = project_dir.join("target/ink/incrementer.wasm"); + let wasm = std::fs::read(wasm_path) + .expect("Failed to read the content of the contract binary!"); + + (bundle, wasm) } #[test] @@ -113,7 +119,7 @@ fn verify_equivalent_contracts() { #[ink(message)] pub fn inc(&mut self, by: i32) { - self.value.saturating_add(by); + self.value = self.value.saturating_add(by); } #[ink(message, selector = 0xCACACACA)] @@ -139,11 +145,14 @@ fn verify_equivalent_contracts() { let lib = project_dir.join("lib.rs"); std::fs::write(lib, contract).expect("Failed to write contract lib.rs"); - // Compile reference contract and write bundle in the directory. - let reference_contents = compile_reference_contract(); + // Compile reference contract and write bundle and wasm in the directory. + let (ref_bundle, ref_wasm) = compile_reference_contract(); let bundle = project_dir.join("reference.contract"); - std::fs::write(bundle, reference_contents) + std::fs::write(bundle, ref_bundle) .expect("Failed to write bundle contract to the current dir!"); + let wasm = project_dir.join("reference.wasm"); + std::fs::write(wasm, ref_wasm) + .expect("Failed to write wasm binary to the current dir!"); // when let output: &str = r#""is_verified": true"#; @@ -151,11 +160,21 @@ fn verify_equivalent_contracts() { // then cargo_contract(&project_dir) .arg("verify") + .arg("--contract") .arg("reference.contract") .arg("--output-json") .assert() .success() .stdout(predicates::str::contains(output)); + // and + cargo_contract(&project_dir) + .arg("verify") + .arg("--wasm") + .arg("reference.wasm") + .arg("--output-json") + .assert() + .success() + .stdout(predicates::str::contains(output)); } #[test] @@ -184,7 +203,7 @@ fn verify_different_contracts() { #[ink(message)] pub fn inc(&mut self, by: i32) { - self.value.saturating_add(by); + self.value = self.value.saturating_add(by); } #[ink(message, selector = 0xCBCBCBCB)] @@ -214,11 +233,14 @@ fn verify_different_contracts() { tracing::debug!("Building contract in {}", project_dir.to_string_lossy()); cargo_contract(&project_dir).arg("build").assert().success(); - // Compile reference contract and write bundle in the directory. - let reference_contents = compile_reference_contract(); + // Compile reference contract and write bundle and wasm in the directory. + let (ref_bundle, ref_wasm) = compile_reference_contract(); let bundle = project_dir.join("reference.contract"); - std::fs::write(bundle, reference_contents) + std::fs::write(bundle, ref_bundle) .expect("Failed to write bundle contract to the current dir!"); + let wasm = project_dir.join("reference.wasm"); + std::fs::write(wasm, ref_wasm) + .expect("Failed to write wasm binary to the current dir!"); // when let output: &str = r#"Failed to verify the authenticity of `incrementer`"#; @@ -226,9 +248,22 @@ fn verify_different_contracts() { // then cargo_contract(&project_dir) .arg("verify") + .arg("--contract") .arg("reference.contract") .arg("--output-json") .assert() .failure() .stderr(predicates::str::contains(output)); + // and + + let output: &str = + r#"Failed to verify the authenticity of wasm binary at `reference.wasm`"#; + cargo_contract(&project_dir) + .arg("verify") + .arg("--wasm") + .arg("reference.wasm") + .arg("--output-json") + .assert() + .failure() + .stderr(predicates::str::contains(output)); }