diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c1918161..126b48304 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 +- Add Rust specific build info to metadata - [#680](https://github.com/paritytech/cargo-contract/pull/680) + ### Changed - Removed requirement to install binaryen. The `wasm-opt` tool is now compiled into `cargo-contract`. diff --git a/crates/cargo-contract/src/cmd/build/mod.rs b/crates/cargo-contract/src/cmd/build/mod.rs index ee6863462..b3fd8f429 100644 --- a/crates/cargo-contract/src/cmd/build/mod.rs +++ b/crates/cargo-contract/src/cmd/build/mod.rs @@ -67,6 +67,9 @@ use std::{ /// This is the maximum number of pages available for a contract to allocate. const MAX_MEMORY_PAGES: u32 = 16; +/// Version of the currently executing `cargo-contract` binary. +const VERSION: &str = env!("CARGO_PKG_VERSION"); + /// Arguments to use when executing `build` or `check` commands. #[derive(Default)] pub(crate) struct ExecuteArgs { @@ -284,6 +287,7 @@ fn exec_cargo_for_wasm_target( let cargo_build = |manifest_path: &ManifestPath| { let target_dir = &crate_metadata.target_directory; let target_dir = format!("--target-dir={}", target_dir.to_string_lossy()); + let mut args = vec![ "--target=wasm32-unknown-unknown", "-Zbuild-std", @@ -576,6 +580,8 @@ pub fn assert_debug_mode_supported(ink_version: &Version) -> anyhow::Result<()> /// /// It does so by invoking `cargo build` and then post processing the final binary. pub(crate) fn execute(args: ExecuteArgs) -> Result { + use crate::cmd::metadata::BuildInfo; + let ExecuteArgs { manifest_path, verbosity, @@ -596,7 +602,9 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { assert_debug_mode_supported(&crate_metadata.ink_version)?; } - let build = || -> Result { + let build = || -> Result<(OptimizationResult, BuildInfo)> { + use crate::cmd::metadata::WasmOptSettings; + if skip_linting { maybe_println!( verbosity, @@ -650,7 +658,26 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { &crate_metadata.contract_artifact_name, )?; - Ok(optimization_result) + let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) { + version + } else { + anyhow::bail!( + "Unable to parse version number for the currently running \ + `cargo-contract` binary." + ); + }; + + let build_info = BuildInfo { + rust_toolchain: crate::util::rust_toolchain()?, + cargo_contract_version, + build_mode, + wasm_opt_settings: WasmOptSettings { + optimization_passes, + keep_debug_symbols, + }, + }; + + Ok((optimization_result, build_info)) }; let (opt_result, metadata_result) = match build_artifact { @@ -689,11 +716,11 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { (None, None) } BuildArtifacts::CodeOnly => { - let optimization_result = build()?; + let (optimization_result, _build_info) = build()?; (Some(optimization_result), None) } BuildArtifacts::All => { - let optimization_result = build()?; + let (optimization_result, build_info) = build()?; let metadata_result = super::metadata::execute( &crate_metadata, @@ -702,7 +729,9 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { verbosity, build_artifact.steps(), &unstable_flags, + build_info, )?; + (Some(optimization_result), Some(metadata_result)) } }; diff --git a/crates/cargo-contract/src/cmd/metadata.rs b/crates/cargo-contract/src/cmd/metadata.rs index 0e4420329..e2ecdd06a 100644 --- a/crates/cargo-contract/src/cmd/metadata.rs +++ b/crates/cargo-contract/src/cmd/metadata.rs @@ -22,7 +22,9 @@ use crate::{ ManifestPath, Workspace, }, + BuildMode, Network, + OptimizationPasses, UnstableFlags, Verbosity, }; @@ -73,6 +75,40 @@ struct ExtendedMetadataResult { user: Option, } +/// Information about the settings used to build a particular ink! contract. +/// +/// Note that this should be an optional part of the metadata since it may not necessarily +/// translate to other languages (e.g ask!, Solidity). +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct BuildInfo { + /// The Rust toolchain used to build the contract. + pub rust_toolchain: String, + /// The version of `cargo-contract` used to build the contract. + pub cargo_contract_version: Version, + /// The type of build that was used when building the contract. + pub build_mode: BuildMode, + /// Information about the `wasm-opt` optimization settings. + pub wasm_opt_settings: WasmOptSettings, +} + +impl TryFrom for serde_json::Map { + type Error = serde_json::Error; + + fn try_from(build_info: BuildInfo) -> Result { + let tmp = serde_json::to_string(&build_info)?; + serde_json::from_str(&tmp) + } +} + +/// Settings used when optimizing the Wasm binary using Binaryen's `wasm-opt`. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WasmOptSettings { + /// The level of optimization used during the `wasm-opt` run. + pub optimization_passes: OptimizationPasses, + /// Whether or not the Wasm name section should be kept. + pub keep_debug_symbols: bool, +} + /// Generates a file with metadata describing the ABI of the smart contract. /// /// It does so by generating and invoking a temporary workspace member. @@ -83,6 +119,7 @@ pub(crate) fn execute( verbosity: Verbosity, total_steps: usize, unstable_options: &UnstableFlags, + build_info: BuildInfo, ) -> Result { let target_directory = crate_metadata.target_directory.clone(); let out_path_metadata = target_directory.join(METADATA_FILE); @@ -95,7 +132,7 @@ pub(crate) fn execute( source, contract, user, - } = extended_metadata(crate_metadata, final_contract_wasm)?; + } = extended_metadata(crate_metadata, final_contract_wasm, build_info)?; let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { let mut current_progress = 5; @@ -171,6 +208,7 @@ pub(crate) fn execute( fn extended_metadata( crate_metadata: &CrateMetadata, final_contract_wasm: &Path, + build_info: BuildInfo, ) -> Result { let contract_package = &crate_metadata.root_package; let ink_version = &crate_metadata.ink_version; @@ -193,7 +231,13 @@ fn extended_metadata( let compiler = SourceCompiler::new(Compiler::RustC, rust_version); let wasm = fs::read(final_contract_wasm)?; let hash = blake2_hash(wasm.as_slice()); - Source::new(Some(SourceWasm::new(wasm)), hash, lang, compiler) + Source::new( + Some(SourceWasm::new(wasm)), + hash, + lang, + compiler, + Some(build_info.try_into()?), + ) }; // Required contract fields diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index 901f4506b..afdbf2363 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -106,7 +106,7 @@ impl FromStr for HexData { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub enum OptimizationPasses { Zero, One, @@ -298,7 +298,7 @@ impl Default for BuildArtifacts { } /// The mode to build the contract in. -#[derive(Eq, PartialEq, Copy, Clone, Debug, serde::Serialize)] +#[derive(Eq, PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum BuildMode { /// Functionality to output debug messages is build into the contract. Debug, diff --git a/crates/cargo-contract/src/util/mod.rs b/crates/cargo-contract/src/util/mod.rs index e4c45ba84..a92ce9359 100644 --- a/crates/cargo-contract/src/util/mod.rs +++ b/crates/cargo-contract/src/util/mod.rs @@ -65,6 +65,14 @@ fn assert_channel() -> Result<()> { } } +// Returns the current Rust toolchain formatted by `-`. +pub(crate) fn rust_toolchain() -> Result { + let meta = rustc_version::version_meta()?; + let toolchain = format!("{:?}-{}", meta.channel, meta.host,).to_lowercase(); + + Ok(toolchain) +} + /// Invokes `cargo` with the subcommand `command` and the supplied `args`. /// /// In case `working_dir` is set, the command will be invoked with that folder diff --git a/crates/metadata/src/lib.rs b/crates/metadata/src/lib.rs index b5a5b558f..4735dabaf 100644 --- a/crates/metadata/src/lib.rs +++ b/crates/metadata/src/lib.rs @@ -28,7 +28,9 @@ //! let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0)); //! let compiler = SourceCompiler::new(Compiler::RustC, Version::parse("1.46.0-nightly").unwrap()); //! let wasm = SourceWasm::new(vec![0u8]); -//! let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler); +//! // Optional information about how the contract was build +//! let build_info: Map = Map::new(); +//! let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler, Some(build_info)); //! let contract = Contract::builder() //! .name("incrementer".to_string()) //! .version(Version::new(2, 1, 0)) @@ -137,6 +139,11 @@ pub struct Source { /// with the metadata. #[serde(skip_serializing_if = "Option::is_none")] pub wasm: Option, + /// Extra information about the environment in which the contract was built. + /// + /// Useful for producing deterministic builds. + #[serde(skip_serializing_if = "Option::is_none")] + pub build_info: Option>, } impl Source { @@ -146,12 +153,14 @@ impl Source { hash: CodeHash, language: SourceLanguage, compiler: SourceCompiler, + build_info: Option>, ) -> Self { Source { hash, language, compiler, wasm, + build_info, } } } @@ -646,7 +655,25 @@ mod tests { Version::parse("1.46.0-nightly").unwrap(), ); let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]); - let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler); + let build_info = json! { + { + "example_compiler_version": 42, + "example_settings": [], + "example_name": "increment" + } + } + .as_object() + .unwrap() + .clone(); + + let source = Source::new( + Some(wasm), + CodeHash([0u8; 32]), + language, + compiler, + Some(build_info), + ); + let contract = Contract::builder() .name("incrementer") .version(Version::new(2, 1, 0)) @@ -690,7 +717,12 @@ mod tests { "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "language": "ink! 2.1.0", "compiler": "rustc 1.46.0-nightly", - "wasm": "0x000102" + "wasm": "0x000102", + "build_info": { + "example_compiler_version": 42, + "example_settings": [], + "example_name": "increment" + } }, "contract": { "name": "incrementer", @@ -729,7 +761,7 @@ mod tests { Compiler::RustC, Version::parse("1.46.0-nightly").unwrap(), ); - let source = Source::new(None, CodeHash([0u8; 32]), language, compiler); + let source = Source::new(None, CodeHash([0u8; 32]), language, compiler, None); let contract = Contract::builder() .name("incrementer") .version(Version::new(2, 1, 0)) @@ -782,7 +814,24 @@ mod tests { Version::parse("1.46.0-nightly").unwrap(), ); let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]); - let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler); + let build_info = json! { + { + "example_compiler_version": 42, + "example_settings": [], + "example_name": "increment", + } + } + .as_object() + .unwrap() + .clone(); + + let source = Source::new( + Some(wasm), + CodeHash([0u8; 32]), + language, + compiler, + Some(build_info), + ); let contract = Contract::builder() .name("incrementer") .version(Version::new(2, 1, 0))