Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify raw Wasm in cargo contract verify #1551

Merged
merged 9 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
99 changes: 94 additions & 5 deletions crates/cargo-contract/src/cmd/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use anyhow::{
};
use colored::Colorize;
use contract_build::{
code_hash,
execute,
verbose_eprintln,
BuildArtifacts,
Expand All @@ -31,7 +32,10 @@ use contract_build::{
Verbosity,
VerbosityFlags,
};
use contract_metadata::ContractMetadata;
use contract_metadata::{
CodeHash,
ContractMetadata,
};

use std::{
fs::File,
Expand All @@ -49,7 +53,12 @@ pub struct VerifyCommand {
manifest_path: Option<PathBuf>,
/// The reference Wasm contract (`*.contract`) that the workspace will be checked
/// against.
contract: PathBuf,
#[clap(long)]
contract: Option<PathBuf>,
/// The reference Wasm contract binary (`*.wasm`) that the workspace will be checked
/// against.
#[clap(long, conflicts_with = "contract")]
wasm: Option<PathBuf>,
/// Denotes if output should be printed to stdout.
#[clap(flatten)]
verbosity: VerbosityFlags,
Expand All @@ -62,9 +71,90 @@ impl VerifyCommand {
pub fn run(&self) -> Result<VerificationResult> {
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<VerificationResult> {
// 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<VerificationResult> {
// 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()))?;

Expand Down Expand Up @@ -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 {}",
Expand All @@ -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 {}.",
Expand Down
61 changes: 48 additions & 13 deletions crates/cargo-contract/tests/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ fn cargo_contract<P: AsRef<Path>>(path: P) -> assert_cmd::Command {
cmd
}

/// Compile the reference contract and return a byte array of its bundle.
fn compile_reference_contract() -> Vec<u8> {
/// Compile the reference contract and return a byte array of its bundle and raw wasm
/// binary.
fn compile_reference_contract() -> (Vec<u8>, Vec<u8>) {
let contract = r#"
#![cfg_attr(not(feature = "std"), no_std, no_main)]

Expand All @@ -48,7 +49,7 @@ fn compile_reference_contract() -> Vec<u8> {

#[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)]
Expand Down Expand Up @@ -82,9 +83,14 @@ fn compile_reference_contract() -> Vec<u8> {
.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]
Expand Down Expand Up @@ -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)]
Expand All @@ -139,23 +145,36 @@ 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"#;

// 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]
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -214,21 +233,37 @@ 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`"#;

// 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));
}
Loading