From 0929ce8a781b5b421be663cbec4adbe052e9e6c4 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 3 Jul 2020 12:21:09 +0100 Subject: [PATCH 01/52] Generate main.rs with quote for passing extension metadata --- Cargo.lock | 1 + Cargo.toml | 1 + src/workspace/metadata.rs | 60 +++++++++++++++++++---- templates/tools/generate-metadata/main.rs | 13 ----- 4 files changed, 53 insertions(+), 22 deletions(-) delete mode 100644 templates/tools/generate-metadata/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0f1f638fb..c478f597a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,7 @@ dependencies = [ "parity-wasm", "pretty_assertions", "pwasm-utils", + "quote", "rustc_version", "serde_json", "sp-core", diff --git a/Cargo.toml b/Cargo.toml index 932678646..b10f3fc81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ cargo-xbuild = "0.5.32" rustc_version = "0.2.3" serde_json = "1.0" tempfile = "3.1.0" +quote = "1.0" # dependencies for optional extrinsics feature async-std = { version = "=1.5.0", optional = true } diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index fad12c373..32dcd73db 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -18,7 +18,8 @@ use anyhow::Result; use std::{fs, path::Path}; use toml::value; -/// Generates a cargo workspace package which will be invoked to generate contract metadata. +/// Generates a cargo workspace package `metadata-gen` which will be invoked via `cargo run` to +/// generate contract metadata. /// /// # Note /// @@ -28,7 +29,7 @@ pub(super) fn generate_package>( target_dir: P, contract_package_name: &str, ink_lang_dependency: value::Table, - mut ink_abi_dependency: value::Table, + ink_abi_dependency: value::Table, ) -> Result<()> { let dir = target_dir.as_ref(); log::debug!( @@ -37,17 +38,31 @@ pub(super) fn generate_package>( dir.display() ); - let cargo_toml = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); - let main_rs = include_str!("../../templates/tools/generate-metadata/main.rs"); + let main_rs = generate_main(); + let cargo_toml = generate_cargo_toml(contract_package_name, ink_lang_dependency, ink_abi_dependency)?; - let mut cargo_toml: value::Table = toml::from_str(cargo_toml)?; + fs::write(dir.join("Cargo.toml"), cargo_toml)?; + fs::write(dir.join("main.rs"), main_rs)?; + Ok(()) +} + +/// Generates the `Cargo.toml` file for the `metadata-gen` package +fn generate_cargo_toml( + contract_package_name: &str, + ink_lang_dependency: value::Table, + mut ink_abi_dependency: value::Table +) -> Result { + let template = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); + let mut cargo_toml: value::Table = toml::from_str(template)?; + + // get a mutable reference to the dependencies section let deps = cargo_toml .get_mut("dependencies") .expect("[dependencies] section specified in the template") .as_table_mut() .expect("[dependencies] is a table specified in the template"); - // initialize contract dependency + // initialize the contract dependency let contract = deps .get_mut("contract") .expect("contract dependency specified in the template") @@ -63,9 +78,36 @@ pub(super) fn generate_package>( // add ink dependencies copied from contract manifest deps.insert("ink_lang".into(), ink_lang_dependency.into()); deps.insert("ink_abi".into(), ink_abi_dependency.into()); + let cargo_toml = toml::to_string(&cargo_toml)?; + Ok(cargo_toml) +} - fs::write(dir.join("Cargo.toml"), cargo_toml)?; - fs::write(dir.join("main.rs"), main_rs)?; - Ok(()) +/// Generate a `main.rs` to invoke `__ink_generate_metadata` +fn generate_main() -> String { + quote::quote! ( + extern crate contract; + + extern "Rust" { + fn __ink_generate_metadata( + extension: ::ink_metadata::InkProjectExtension + ) -> ::ink_metadata::InkProject; + } + + fn main() -> Result<(), std::io::Error> { + let extension = + InkProjectContract::build() + .name("testing") + .version(::ink_metadata::Version::new(0, 1, 0)) + .authors(vec!["author@example.com"]) + .documentation(::ink_metadata::Url::parse("http://example.com").unwrap()) + .done(); + + let ink_project = unsafe { __ink_generate_metadata(extension) }; + let contents = serde_json::to_string_pretty(&ink_project)?; + std::fs::create_dir("target").ok(); + std::fs::write("target/metadata.json", contents)?; + Ok(()) + } + ).to_string() } diff --git a/templates/tools/generate-metadata/main.rs b/templates/tools/generate-metadata/main.rs deleted file mode 100644 index 3d5eeb233..000000000 --- a/templates/tools/generate-metadata/main.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate contract; - -extern "Rust" { - fn __ink_generate_metadata() -> ink_abi::InkProject; -} - -fn main() -> Result<(), std::io::Error> { - let ink_project = unsafe { __ink_generate_metadata() }; - let contents = serde_json::to_string_pretty(&ink_project)?; - std::fs::create_dir("target").ok(); - std::fs::write("target/metadata.json", contents)?; - Ok(()) -} From 8c0b8bc1dd90a18a6bd62511ff4306dc766a0422 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 3 Jul 2020 12:24:54 +0100 Subject: [PATCH 02/52] Update template to scale-info 0.3 --- templates/new/_Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index db4f2fe39..0ed3c673c 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -11,7 +11,7 @@ ink_core = { git = "https://github.com/paritytech/ink", branch = "master", packa ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } -scale-info = { version = "0.2", default-features = false, features = ["derive"], optional = true } +scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } [lib] name = "{{name}}" From 955f59ecdf99cdeddb11926cf3395852aefc3ef6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 3 Jul 2020 12:34:19 +0100 Subject: [PATCH 03/52] Rename ink_abi package to ink_metadata --- src/workspace/manifest.rs | 4 ++-- src/workspace/metadata.rs | 16 ++++++++-------- templates/new/_Cargo.toml | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/workspace/manifest.rs b/src/workspace/manifest.rs index d02c59756..af9e4014f 100644 --- a/src/workspace/manifest.rs +++ b/src/workspace/manifest.rs @@ -372,9 +372,9 @@ impl Manifest { }; let ink_lang = get_dependency("ink_lang")?; - let ink_abi = get_dependency("ink_abi")?; + let ink_metadata = get_dependency("ink_metadata")?; - metadata::generate_package(dir, name, ink_lang.clone(), ink_abi.clone())?; + metadata::generate_package(dir, name, ink_lang.clone(), ink_metadata.clone())?; } let updated_toml = toml::to_string(&self.toml)?; diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index 32dcd73db..4e5801f49 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -29,7 +29,7 @@ pub(super) fn generate_package>( target_dir: P, contract_package_name: &str, ink_lang_dependency: value::Table, - ink_abi_dependency: value::Table, + ink_metadata_dependency: value::Table, ) -> Result<()> { let dir = target_dir.as_ref(); log::debug!( @@ -39,7 +39,7 @@ pub(super) fn generate_package>( ); let main_rs = generate_main(); - let cargo_toml = generate_cargo_toml(contract_package_name, ink_lang_dependency, ink_abi_dependency)?; + let cargo_toml = generate_cargo_toml(contract_package_name, ink_lang_dependency, ink_metadata_dependency)?; fs::write(dir.join("Cargo.toml"), cargo_toml)?; fs::write(dir.join("main.rs"), main_rs)?; @@ -50,7 +50,7 @@ pub(super) fn generate_package>( fn generate_cargo_toml( contract_package_name: &str, ink_lang_dependency: value::Table, - mut ink_abi_dependency: value::Table + mut ink_metadata_dependency: value::Table ) -> Result { let template = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); let mut cargo_toml: value::Table = toml::from_str(template)?; @@ -70,14 +70,14 @@ fn generate_cargo_toml( .expect("contract dependency is a table specified in the template"); contract.insert("package".into(), contract_package_name.into()); - // make ink_abi dependency use default features - ink_abi_dependency.remove("default-features"); - ink_abi_dependency.remove("features"); - ink_abi_dependency.remove("optional"); + // make ink_metadata dependency use default features + ink_metadata_dependency.remove("default-features"); + ink_metadata_dependency.remove("features"); + ink_metadata_dependency.remove("optional"); // add ink dependencies copied from contract manifest deps.insert("ink_lang".into(), ink_lang_dependency.into()); - deps.insert("ink_abi".into(), ink_abi_dependency.into()); + deps.insert("ink_metadata".into(), ink_metadata_dependency.into()); let cargo_toml = toml::to_string(&cargo_toml)?; Ok(cargo_toml) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index 0ed3c673c..df71d79d9 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -5,7 +5,7 @@ authors = ["[your_name] <[your_email]>"] edition = "2018" [dependencies] -ink_abi = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_abi", default-features = false, features = ["derive"], optional = true } +ink_metadata = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } @@ -24,7 +24,7 @@ crate-type = [ [features] default = ["std"] std = [ - "ink_abi/std", + "ink_metadata/std", "ink_core/std", "ink_primitives/std", "scale/std", From 9359c4c6280930de4594b6575cd240271ff4cc68 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 3 Jul 2020 12:39:01 +0100 Subject: [PATCH 04/52] Temporarily change ink dependency branch to aj-extra-metadata --- templates/new/_Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index df71d79d9..816682ace 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -5,10 +5,10 @@ authors = ["[your_name] <[your_email]>"] edition = "2018" [dependencies] -ink_metadata = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } -ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } -ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } -ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } +ink_metadata = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } +ink_primitives = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", default-features = false } +ink_core = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_core", default-features = false } +ink_lang = { git = "https://github.com/paritytech/ink", branch = "j", package = "ink_lang", default-features = false } scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } From 42a57eed4549111093db562df714f388178f00e8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 3 Jul 2020 16:10:15 +0100 Subject: [PATCH 05/52] Fix up metadata generation codegen --- src/workspace/metadata.rs | 52 +++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index 4e5801f49..5a2ea7cdb 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -94,16 +94,54 @@ fn generate_main() -> String { ) -> ::ink_metadata::InkProject; } + use ::ink_metadata::{ + Compiler, + CompilerInfo, + InkProjectContract, + InkProjectExtension, + InkProjectSource, + InkProjectUser, + Language, + SourceCompiler, + SourceLanguage, + Version, + Url, + }; + fn main() -> Result<(), std::io::Error> { - let extension = - InkProjectContract::build() - .name("testing") - .version(::ink_metadata::Version::new(0, 1, 0)) - .authors(vec!["author@example.com"]) - .documentation(::ink_metadata::Url::parse("http://example.com").unwrap()) - .done(); + // todo: pass in the following as args to generate_main() + let wasm_hash = [0u8; 32]; + let ink_version = Version::new(2, 1, 0); + let rustc_version = Version::new(1, 46, 0); + + let extension = { + let language = SourceLanguage::new(Language::Ink, ink_version.clone()); + let compiler = SourceCompiler::new( + CompilerInfo::new(Compiler::Ink, ink_version), + CompilerInfo::new(Compiler::RustC, rustc_version), + ); + let source = InkProjectSource::new( + wasm_hash, + language, + compiler, + ); + + let contract = + InkProjectContract::build() + .name("testing") + .version(Version::new(0, 1, 0)) + .authors(vec!["author@example.com"]) + .documentation(Url::parse("http://example.com").unwrap()) + .done(); + + // todo: pass in user args + let user: Option = None; + + InkProjectExtension::new(source, contract, user) + }; let ink_project = unsafe { __ink_generate_metadata(extension) }; + let contents = serde_json::to_string_pretty(&ink_project)?; std::fs::create_dir("target").ok(); std::fs::write("target/metadata.json", contents)?; From 3d2c57a9bc30bdea7dea2a5f6269e58d9382a15c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 08:21:38 +0100 Subject: [PATCH 06/52] Promote metadata mod to directory --- src/cmd/{metadata.rs => metadata/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/cmd/{metadata.rs => metadata/mod.rs} (100%) diff --git a/src/cmd/metadata.rs b/src/cmd/metadata/mod.rs similarity index 100% rename from src/cmd/metadata.rs rename to src/cmd/metadata/mod.rs From f3673557d39b0c5f516a69518a825fb91c262130 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 08:55:33 +0100 Subject: [PATCH 07/52] Move InkProjectExtension data structure over from ink! PR --- Cargo.lock | 18 +- Cargo.toml | 7 +- src/cmd/metadata/mod.rs | 2 + src/cmd/metadata/project.rs | 396 ++++++++++++++++++++++++++++++++++++ 4 files changed, 417 insertions(+), 6 deletions(-) create mode 100644 src/cmd/metadata/project.rs diff --git a/Cargo.lock b/Cargo.lock index c478f597a..38908c47a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,8 +362,9 @@ dependencies = [ "parity-wasm", "pretty_assertions", "pwasm-utils", - "quote", "rustc_version", + "semver 0.10.0", + "serde", "serde_json", "sp-core", "structopt", @@ -402,7 +403,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202" dependencies = [ - "semver", + "semver 0.9.0", "serde", "serde_derive", "serde_json", @@ -2376,7 +2377,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", ] [[package]] @@ -2487,6 +2488,16 @@ dependencies = [ "serde", ] +[[package]] +name = "semver" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "394cec28fa623e00903caf7ba4fa6fb9a0e260280bb8cdbbba029611108a0190" +dependencies = [ + "semver-parser", + "serde", +] + [[package]] name = "semver-parser" version = "0.7.0" @@ -3532,6 +3543,7 @@ dependencies = [ "idna 0.2.0", "matches", "percent-encoding 2.1.0", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b10f3fc81..2d263d69a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,9 +31,11 @@ colored = "1.9" toml = "0.5.4" cargo-xbuild = "0.5.32" rustc_version = "0.2.3" +semver = { version = "0.10.0", features = ["serde"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = "1.0" tempfile = "3.1.0" -quote = "1.0" +url = { version = "2.1.1", features = ["serde"] } # dependencies for optional extrinsics feature async-std = { version = "=1.5.0", optional = true } @@ -41,7 +43,6 @@ sp-core = { version = "2.0.0-rc3", optional = true } subxt = { version = "0.9.0", package = "substrate-subxt", optional = true } futures = { version = "0.3.2", optional = true } hex = { version = "0.4.0", optional = true } -url = { version = "2.1.1", optional = true } [build-dependencies] anyhow = "1.0" @@ -55,5 +56,5 @@ wabt = "0.9.2" [features] default = [] -extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex", "url"] +extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex"] test-ci-only = [] diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 36146d3cb..8b189f545 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with ink!. If not, see . +mod project; + use crate::{ util, workspace::{ManifestPath, Workspace}, diff --git a/src/cmd/metadata/project.rs b/src/cmd/metadata/project.rs new file mode 100644 index 000000000..ce65afecc --- /dev/null +++ b/src/cmd/metadata/project.rs @@ -0,0 +1,396 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// ink! 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. +// +// ink! 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 ink!. If not, see . + +use core::{ + fmt::{ + Display, + Formatter, + Result as DisplayResult, + Write, + }, + marker::PhantomData, +}; +use semver::Version; +use serde::{ + Serialize, + Serializer, +}; +use serde_json::{ + Map, + Value, +}; +use url::Url; + +/// Additional metadata generated by cargo-contract. +#[derive(Debug, Serialize)] +pub struct InkProjectExtension { + source: InkProjectSource, + contract: InkProjectContract, + #[serde(skip_serializing_if = "Option::is_none")] + user: Option, +} + +impl InkProjectExtension { + /// Constructs a new InkProjectExtension. + pub fn new( + source: InkProjectSource, + contract: InkProjectContract, + user: Option, + ) -> Self { + InkProjectExtension { + source, + contract, + user, + } + } +} + +#[derive(Debug, Serialize)] +pub struct InkProjectSource { + #[serde(serialize_with = "serialize_as_byte_str")] + hash: [u8; 32], + language: SourceLanguage, + compiler: SourceCompiler, +} + +impl InkProjectSource { + /// Constructs a new InkProjectSource. + pub fn new( + hash: [u8; 32], + language: SourceLanguage, + compiler: SourceCompiler, + ) -> Self { + InkProjectSource { + hash, + language, + compiler, + } + } +} + +/// The language and version in which a smart contract is written. +#[derive(Debug)] +pub struct SourceLanguage { + language: Language, + version: Version, +} + +impl SourceLanguage { + /// Constructs a new SourceLanguage. + pub fn new(language: Language, version: Version) -> Self { + SourceLanguage { language, version } + } +} + +impl Serialize for SourceLanguage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{} {}", self.language, self.version)) + } +} + +/// The language in which the smart contract is written. +#[derive(Debug)] +pub enum Language { + Ink, + Solidity, + AssemblyScript, + Other(&'static str), +} + +impl Display for Language { + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + match self { + Self::Ink => write!(f, "ink!"), + Self::Solidity => write!(f, "Solidity"), + Self::AssemblyScript => write!(f, "AssemblyScript"), + Self::Other(lang) => write!(f, "{}", lang), + } + } +} + +/// A compiler used to compile a smart contract. +#[derive(Debug)] +pub struct SourceCompiler { + compiler: Compiler, + version: Version, +} + +impl Serialize for SourceCompiler { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{} {}", self.compiler, self.version)) + } +} + +impl SourceCompiler { + pub fn new(compiler: Compiler, version: Version) -> Self { + SourceCompiler { compiler, version } + } +} + +/// Compilers used to compile a smart contract. +#[derive(Debug, Serialize)] +pub enum Compiler { + RustC, + Solang, + Other(&'static str), +} + +impl Display for Compiler { + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + match self { + Self::RustC => write!(f, "rustc"), + Self::Solang => write!(f, "solang"), + Self::Other(other) => write!(f, "{}", other), + } + } +} + +/// Metadata about a smart contract. +#[derive(Debug, Serialize)] +pub struct InkProjectContract { + name: String, + version: Version, + authors: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + documentation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + repository: Option, + #[serde(skip_serializing_if = "Option::is_none")] + homepage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + license: Option, +} + +impl InkProjectContract { + /// Constructs a new InkProjectContractBuilder. + pub fn build() -> InkProjectContractBuilder< + Missing, + Missing, + Missing, + > { + InkProjectContractBuilder { + contract: Self { + name: Default::default(), + version: Default::default(), + authors: vec![], + description: None, + documentation: None, + repository: None, + homepage: None, + license: None, + }, + marker: Default::default(), + } + } +} + +/// The license of a smart contract +#[derive(Debug, Serialize)] +pub enum License { + /// An [SPDX identifier](https://spdx.org/licenses/) + SpdxId(String), + /// A URL to a custom license + Link(Url), +} + +/// Additional user defined metadata, can be any valid json. +#[derive(Debug, Serialize)] +pub struct InkProjectUser { + #[serde(flatten)] + json: serde_json::Map, +} + +impl InkProjectUser { + /// Constructs a new InkProjectUser + pub fn new(json: Map) -> Self { + InkProjectUser { json } + } + + pub fn from_str(json: &str) -> serde_json::Result { + serde_json::from_str(json.as_ref()).map(Self::new) + } +} + +/// Type state for builders to tell that some mandatory state has not yet been set +/// yet or to fail upon setting the same state multiple times. +pub struct Missing(PhantomData S>); + +mod state { + //! Type states that tell what state of the project metadata has not + //! yet been set properly for a valid construction. + + /// Type state for the name of the project. + pub struct Name; + + /// Type state for the version of the project. + pub struct Version; + + /// Type state for the authors of the project. + pub struct Authors; +} + +/// Build an [`InkProjectContract`], ensuring required fields are supplied +/// +/// # Example +/// +/// ``` +/// # use crate::ink_metadata::InkProjectContract; +/// # use semver::Version; +/// # use url::Url; +/// // contract metadata with the minimum set of required fields +/// let metadata1: InkProjectContract = +/// InkProjectContract::build() +/// .name("example") +/// .version(Version::new(0, 1, 0)) +/// .authors(vec!["author@example.com"]) +/// .done(); +/// +/// // contract metadata with optional fields +/// let metadata2: InkProjectContract = +/// InkProjectContract::build() +/// .name("example") +/// .version(Version::new(0, 1, 0)) +/// .authors(vec!["author@example.com"]) +/// .description("description") +/// .documentation(Url::parse("http://example.com").unwrap()) +/// .repository(Url::parse("http://example.com").unwrap()) +/// .homepage(Url::parse("http://example.com").unwrap()) +/// .done(); +/// ``` +#[allow(clippy::type_complexity)] +pub struct InkProjectContractBuilder { + contract: InkProjectContract, + marker: PhantomData (Name, Version, Authors)>, +} + +impl InkProjectContractBuilder, V, A> { + /// Set the contract name (required) + pub fn name(self, name: S) -> InkProjectContractBuilder + where + S: AsRef, + { + InkProjectContractBuilder { + contract: InkProjectContract { + name: name.as_ref().to_owned(), + ..self.contract + }, + marker: PhantomData, + } + } +} + +impl InkProjectContractBuilder, A> { + /// Set the contract version (required) + pub fn version(self, version: V) -> InkProjectContractBuilder + where + V: Into, + { + InkProjectContractBuilder { + contract: InkProjectContract { + version, + ..self.contract + }, + marker: PhantomData, + } + } +} + +impl InkProjectContractBuilder> { + /// Set the contract authors (required) + pub fn authors( + self, + authors: I, + ) -> InkProjectContractBuilder + where + I: IntoIterator, + S: AsRef, + { + InkProjectContractBuilder { + contract: InkProjectContract { + authors: authors.into_iter().map(|s| s.as_ref().into()).collect(), + ..self.contract + }, + marker: PhantomData, + } + } +} + +impl InkProjectContractBuilder { + /// Set the contract description (optional) + pub fn description(mut self, description: S) -> Self + where + S: AsRef, + { + self.contract.description = Some(description.as_ref().to_owned()); + self + } + + /// Set the contract documentation url (optional) + pub fn documentation(mut self, documentation: Url) -> Self + { + self.contract.documentation = Some(documentation); + self + } + + /// Set the contract documentation url (optional) + pub fn repository(mut self, repository: Url) -> Self { + self.contract.repository = Some(repository); + self + } + + /// Set the contract homepage url (optional) + pub fn homepage(mut self, homepage: Url) -> Self { + self.contract.homepage = Some(homepage.into()); + self + } + + /// Set the contract license (optional) + pub fn license(mut self, license: License) -> Self { + self.contract.license = Some(license); + self + } +} + +impl InkProjectContractBuilder { + pub fn done(self) -> InkProjectContract { + self.contract + } +} + +/// Serializes the given bytes as byte string. +fn serialize_as_byte_str(bytes: &[u8], serializer: S) -> Result + where + S: serde::Serializer, +{ + if bytes.is_empty() { + // Return empty string without prepended `0x`. + return serializer.serialize_str("") + } + let mut hex = String::with_capacity(bytes.len() * 2 + 2); + write!(hex, "0x").expect("failed writing to string"); + for byte in bytes { + write!(hex, "{:02x}", byte).expect("failed writing to string"); + } + serializer.serialize_str(&hex) +} From c29225b7fa1019145f892f4c3e8030462c1edec6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 09:01:31 +0100 Subject: [PATCH 08/52] Revert "Generate main.rs with quote for passing extension metadata" This reverts commit 0929ce8a --- Cargo.toml | 1 + src/workspace/metadata.rs | 102 +++------------------- templates/tools/generate-metadata/main.rs | 13 +++ 3 files changed, 26 insertions(+), 90 deletions(-) create mode 100644 templates/tools/generate-metadata/main.rs diff --git a/Cargo.toml b/Cargo.toml index 2d263d69a..71f0b05d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ sp-core = { version = "2.0.0-rc3", optional = true } subxt = { version = "0.9.0", package = "substrate-subxt", optional = true } futures = { version = "0.3.2", optional = true } hex = { version = "0.4.0", optional = true } +url = { version = "2.1.1", optional = true } [build-dependencies] anyhow = "1.0" diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index 5a2ea7cdb..89052b35c 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -29,7 +29,7 @@ pub(super) fn generate_package>( target_dir: P, contract_package_name: &str, ink_lang_dependency: value::Table, - ink_metadata_dependency: value::Table, + mut ink_abi_dependency: value::Table, ) -> Result<()> { let dir = target_dir.as_ref(); log::debug!( @@ -38,31 +38,17 @@ pub(super) fn generate_package>( dir.display() ); - let main_rs = generate_main(); - let cargo_toml = generate_cargo_toml(contract_package_name, ink_lang_dependency, ink_metadata_dependency)?; + let cargo_toml = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); + let main_rs = include_str!("../../templates/tools/generate-metadata/main.rs"); - fs::write(dir.join("Cargo.toml"), cargo_toml)?; - fs::write(dir.join("main.rs"), main_rs)?; - Ok(()) -} - -/// Generates the `Cargo.toml` file for the `metadata-gen` package -fn generate_cargo_toml( - contract_package_name: &str, - ink_lang_dependency: value::Table, - mut ink_metadata_dependency: value::Table -) -> Result { - let template = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); - let mut cargo_toml: value::Table = toml::from_str(template)?; - - // get a mutable reference to the dependencies section + let mut cargo_toml: value::Table = toml::from_str(cargo_toml)?; let deps = cargo_toml .get_mut("dependencies") .expect("[dependencies] section specified in the template") .as_table_mut() .expect("[dependencies] is a table specified in the template"); - // initialize the contract dependency + // initialize contract dependency let contract = deps .get_mut("contract") .expect("contract dependency specified in the template") @@ -70,82 +56,18 @@ fn generate_cargo_toml( .expect("contract dependency is a table specified in the template"); contract.insert("package".into(), contract_package_name.into()); - // make ink_metadata dependency use default features - ink_metadata_dependency.remove("default-features"); - ink_metadata_dependency.remove("features"); - ink_metadata_dependency.remove("optional"); + // make ink_abi dependency use default features + ink_abi_dependency.remove("default-features"); + ink_abi_dependency.remove("features"); + ink_abi_dependency.remove("optional"); // add ink dependencies copied from contract manifest deps.insert("ink_lang".into(), ink_lang_dependency.into()); deps.insert("ink_metadata".into(), ink_metadata_dependency.into()); let cargo_toml = toml::to_string(&cargo_toml)?; - Ok(cargo_toml) -} - -/// Generate a `main.rs` to invoke `__ink_generate_metadata` -fn generate_main() -> String { - quote::quote! ( - extern crate contract; - - extern "Rust" { - fn __ink_generate_metadata( - extension: ::ink_metadata::InkProjectExtension - ) -> ::ink_metadata::InkProject; - } - use ::ink_metadata::{ - Compiler, - CompilerInfo, - InkProjectContract, - InkProjectExtension, - InkProjectSource, - InkProjectUser, - Language, - SourceCompiler, - SourceLanguage, - Version, - Url, - }; - - fn main() -> Result<(), std::io::Error> { - // todo: pass in the following as args to generate_main() - let wasm_hash = [0u8; 32]; - let ink_version = Version::new(2, 1, 0); - let rustc_version = Version::new(1, 46, 0); - - let extension = { - let language = SourceLanguage::new(Language::Ink, ink_version.clone()); - let compiler = SourceCompiler::new( - CompilerInfo::new(Compiler::Ink, ink_version), - CompilerInfo::new(Compiler::RustC, rustc_version), - ); - let source = InkProjectSource::new( - wasm_hash, - language, - compiler, - ); - - let contract = - InkProjectContract::build() - .name("testing") - .version(Version::new(0, 1, 0)) - .authors(vec!["author@example.com"]) - .documentation(Url::parse("http://example.com").unwrap()) - .done(); - - // todo: pass in user args - let user: Option = None; - - InkProjectExtension::new(source, contract, user) - }; - - let ink_project = unsafe { __ink_generate_metadata(extension) }; - - let contents = serde_json::to_string_pretty(&ink_project)?; - std::fs::create_dir("target").ok(); - std::fs::write("target/metadata.json", contents)?; - Ok(()) - } - ).to_string() + fs::write(dir.join("Cargo.toml"), cargo_toml)?; + fs::write(dir.join("main.rs"), main_rs)?; + Ok(()) } diff --git a/templates/tools/generate-metadata/main.rs b/templates/tools/generate-metadata/main.rs new file mode 100644 index 000000000..3d5eeb233 --- /dev/null +++ b/templates/tools/generate-metadata/main.rs @@ -0,0 +1,13 @@ +extern crate contract; + +extern "Rust" { + fn __ink_generate_metadata() -> ink_abi::InkProject; +} + +fn main() -> Result<(), std::io::Error> { + let ink_project = unsafe { __ink_generate_metadata() }; + let contents = serde_json::to_string_pretty(&ink_project)?; + std::fs::create_dir("target").ok(); + std::fs::write("target/metadata.json", contents)?; + Ok(()) +} From 56c63b10beed732e094d5bf08fff8651be370377 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 09:09:49 +0100 Subject: [PATCH 09/52] Make it compile --- Cargo.toml | 1 - src/cmd/metadata/project.rs | 7 ++----- src/workspace/metadata.rs | 8 ++++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71f0b05d2..2d263d69a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ sp-core = { version = "2.0.0-rc3", optional = true } subxt = { version = "0.9.0", package = "substrate-subxt", optional = true } futures = { version = "0.3.2", optional = true } hex = { version = "0.4.0", optional = true } -url = { version = "2.1.1", optional = true } [build-dependencies] anyhow = "1.0" diff --git a/src/cmd/metadata/project.rs b/src/cmd/metadata/project.rs index ce65afecc..ba2d422b0 100644 --- a/src/cmd/metadata/project.rs +++ b/src/cmd/metadata/project.rs @@ -192,7 +192,7 @@ impl InkProjectContract { InkProjectContractBuilder { contract: Self { name: Default::default(), - version: Default::default(), + version: Version::new(0, 0, 0), authors: vec![], description: None, documentation: None, @@ -302,10 +302,7 @@ impl InkProjectContractBuilder, V, A> { impl InkProjectContractBuilder, A> { /// Set the contract version (required) - pub fn version(self, version: V) -> InkProjectContractBuilder - where - V: Into, - { + pub fn version(self, version: Version) -> InkProjectContractBuilder { InkProjectContractBuilder { contract: InkProjectContract { version, diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index 89052b35c..fcbb5598d 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -29,7 +29,7 @@ pub(super) fn generate_package>( target_dir: P, contract_package_name: &str, ink_lang_dependency: value::Table, - mut ink_abi_dependency: value::Table, + mut ink_metadata_dependency: value::Table, ) -> Result<()> { let dir = target_dir.as_ref(); log::debug!( @@ -57,9 +57,9 @@ pub(super) fn generate_package>( contract.insert("package".into(), contract_package_name.into()); // make ink_abi dependency use default features - ink_abi_dependency.remove("default-features"); - ink_abi_dependency.remove("features"); - ink_abi_dependency.remove("optional"); + ink_metadata_dependency.remove("default-features"); + ink_metadata_dependency.remove("features"); + ink_metadata_dependency.remove("optional"); // add ink dependencies copied from contract manifest deps.insert("ink_lang".into(), ink_lang_dependency.into()); From 972287a6f7fafe3d3301f485627b9ae799b7224d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 09:46:17 +0100 Subject: [PATCH 10/52] Flatten metadata struct --- src/cmd/metadata/{project.rs => contract.rs} | 32 +++++++++++++------- src/cmd/metadata/mod.rs | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) rename src/cmd/metadata/{project.rs => contract.rs} (93%) diff --git a/src/cmd/metadata/project.rs b/src/cmd/metadata/contract.rs similarity index 93% rename from src/cmd/metadata/project.rs rename to src/cmd/metadata/contract.rs index ba2d422b0..d2bf08ad9 100644 --- a/src/cmd/metadata/project.rs +++ b/src/cmd/metadata/contract.rs @@ -34,26 +34,36 @@ use serde_json::{ }; use url::Url; -/// Additional metadata generated by cargo-contract. +const METADATA_VERSION: &str = "0.1.0"; + +/// An entire ink! project for metadata file generation purposes. #[derive(Debug, Serialize)] -pub struct InkProjectExtension { +pub struct ContractMetadata { + metadata_version: semver::Version, source: InkProjectSource, contract: InkProjectContract, #[serde(skip_serializing_if = "Option::is_none")] user: Option, + /// Raw JSON of the metadata generated by the ink! contract itself + #[serde(flatten)] + ink: Map, } -impl InkProjectExtension { - /// Constructs a new InkProjectExtension. - pub fn new( - source: InkProjectSource, - contract: InkProjectContract, - user: Option, - ) -> Self { - InkProjectExtension { +impl ContractMetadata { + /// Construct a new ContractMetadata + pub fn new(source: InkProjectSource, + contract: InkProjectContract, + user: Option, ink: Map) -> Self + { + let metadata_version = semver::Version::parse(METADATA_VERSION) + .expect("METADATA_VERSION is a valid semver string"); + + ContractMetadata { + metadata_version, source, contract, user, + ink, } } } @@ -218,7 +228,7 @@ pub enum License { #[derive(Debug, Serialize)] pub struct InkProjectUser { #[serde(flatten)] - json: serde_json::Map, + json: Map, } impl InkProjectUser { diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 8b189f545..fff98579a 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with ink!. If not, see . -mod project; +mod contract; use crate::{ util, From ac87a894aea404f05caab273e1a0096d8737570e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 09:53:15 +0100 Subject: [PATCH 11/52] Fmt --- src/cmd/metadata/contract.rs | 473 +++++++++++++++++------------------ 1 file changed, 228 insertions(+), 245 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index d2bf08ad9..83ec96e07 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -15,23 +15,12 @@ // along with ink!. If not, see . use core::{ - fmt::{ - Display, - Formatter, - Result as DisplayResult, - Write, - }, - marker::PhantomData, + fmt::{Display, Formatter, Result as DisplayResult, Write}, + marker::PhantomData, }; use semver::Version; -use serde::{ - Serialize, - Serializer, -}; -use serde_json::{ - Map, - Value, -}; +use serde::{Serialize, Serializer}; +use serde_json::{Map, Value}; use url::Url; const METADATA_VERSION: &str = "0.1.0"; @@ -39,207 +28,205 @@ const METADATA_VERSION: &str = "0.1.0"; /// An entire ink! project for metadata file generation purposes. #[derive(Debug, Serialize)] pub struct ContractMetadata { - metadata_version: semver::Version, - source: InkProjectSource, - contract: InkProjectContract, - #[serde(skip_serializing_if = "Option::is_none")] - user: Option, - /// Raw JSON of the metadata generated by the ink! contract itself - #[serde(flatten)] - ink: Map, + metadata_version: semver::Version, + source: InkProjectSource, + contract: InkProjectContract, + #[serde(skip_serializing_if = "Option::is_none")] + user: Option, + /// Raw JSON of the metadata generated by the ink! contract itself + #[serde(flatten)] + ink: Map, } impl ContractMetadata { - /// Construct a new ContractMetadata - pub fn new(source: InkProjectSource, - contract: InkProjectContract, - user: Option, ink: Map) -> Self - { - let metadata_version = semver::Version::parse(METADATA_VERSION) - .expect("METADATA_VERSION is a valid semver string"); - - ContractMetadata { - metadata_version, - source, - contract, - user, - ink, - } - } + /// Construct a new ContractMetadata + pub fn new( + source: InkProjectSource, + contract: InkProjectContract, + user: Option, + ink: Map, + ) -> Self { + let metadata_version = semver::Version::parse(METADATA_VERSION) + .expect("METADATA_VERSION is a valid semver string"); + + ContractMetadata { + metadata_version, + source, + contract, + user, + ink, + } + } } #[derive(Debug, Serialize)] pub struct InkProjectSource { - #[serde(serialize_with = "serialize_as_byte_str")] - hash: [u8; 32], - language: SourceLanguage, - compiler: SourceCompiler, + #[serde(serialize_with = "serialize_as_byte_str")] + hash: [u8; 32], + language: SourceLanguage, + compiler: SourceCompiler, } impl InkProjectSource { - /// Constructs a new InkProjectSource. - pub fn new( - hash: [u8; 32], - language: SourceLanguage, - compiler: SourceCompiler, - ) -> Self { - InkProjectSource { - hash, - language, - compiler, - } - } + /// Constructs a new InkProjectSource. + pub fn new(hash: [u8; 32], language: SourceLanguage, compiler: SourceCompiler) -> Self { + InkProjectSource { + hash, + language, + compiler, + } + } } /// The language and version in which a smart contract is written. #[derive(Debug)] pub struct SourceLanguage { - language: Language, - version: Version, + language: Language, + version: Version, } impl SourceLanguage { - /// Constructs a new SourceLanguage. - pub fn new(language: Language, version: Version) -> Self { - SourceLanguage { language, version } - } + /// Constructs a new SourceLanguage. + pub fn new(language: Language, version: Version) -> Self { + SourceLanguage { language, version } + } } impl Serialize for SourceLanguage { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{} {}", self.language, self.version)) - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{} {}", self.language, self.version)) + } } /// The language in which the smart contract is written. #[derive(Debug)] pub enum Language { - Ink, - Solidity, - AssemblyScript, - Other(&'static str), + Ink, + Solidity, + AssemblyScript, + Other(&'static str), } impl Display for Language { - fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { - match self { - Self::Ink => write!(f, "ink!"), - Self::Solidity => write!(f, "Solidity"), - Self::AssemblyScript => write!(f, "AssemblyScript"), - Self::Other(lang) => write!(f, "{}", lang), - } - } + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + match self { + Self::Ink => write!(f, "ink!"), + Self::Solidity => write!(f, "Solidity"), + Self::AssemblyScript => write!(f, "AssemblyScript"), + Self::Other(lang) => write!(f, "{}", lang), + } + } } /// A compiler used to compile a smart contract. #[derive(Debug)] pub struct SourceCompiler { - compiler: Compiler, - version: Version, + compiler: Compiler, + version: Version, } impl Serialize for SourceCompiler { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{} {}", self.compiler, self.version)) - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{} {}", self.compiler, self.version)) + } } impl SourceCompiler { - pub fn new(compiler: Compiler, version: Version) -> Self { - SourceCompiler { compiler, version } - } + pub fn new(compiler: Compiler, version: Version) -> Self { + SourceCompiler { compiler, version } + } } /// Compilers used to compile a smart contract. #[derive(Debug, Serialize)] pub enum Compiler { - RustC, - Solang, - Other(&'static str), + RustC, + Solang, + Other(&'static str), } impl Display for Compiler { - fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { - match self { - Self::RustC => write!(f, "rustc"), - Self::Solang => write!(f, "solang"), - Self::Other(other) => write!(f, "{}", other), - } - } + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + match self { + Self::RustC => write!(f, "rustc"), + Self::Solang => write!(f, "solang"), + Self::Other(other) => write!(f, "{}", other), + } + } } /// Metadata about a smart contract. #[derive(Debug, Serialize)] pub struct InkProjectContract { - name: String, - version: Version, - authors: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - documentation: Option, - #[serde(skip_serializing_if = "Option::is_none")] - repository: Option, - #[serde(skip_serializing_if = "Option::is_none")] - homepage: Option, - #[serde(skip_serializing_if = "Option::is_none")] - license: Option, + name: String, + version: Version, + authors: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + #[serde(skip_serializing_if = "Option::is_none")] + documentation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + repository: Option, + #[serde(skip_serializing_if = "Option::is_none")] + homepage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + license: Option, } impl InkProjectContract { - /// Constructs a new InkProjectContractBuilder. - pub fn build() -> InkProjectContractBuilder< - Missing, - Missing, - Missing, - > { - InkProjectContractBuilder { - contract: Self { - name: Default::default(), - version: Version::new(0, 0, 0), - authors: vec![], - description: None, - documentation: None, - repository: None, - homepage: None, - license: None, - }, - marker: Default::default(), - } - } + /// Constructs a new InkProjectContractBuilder. + pub fn build() -> InkProjectContractBuilder< + Missing, + Missing, + Missing, + > { + InkProjectContractBuilder { + contract: Self { + name: Default::default(), + version: Version::new(0, 0, 0), + authors: vec![], + description: None, + documentation: None, + repository: None, + homepage: None, + license: None, + }, + marker: Default::default(), + } + } } /// The license of a smart contract #[derive(Debug, Serialize)] pub enum License { - /// An [SPDX identifier](https://spdx.org/licenses/) - SpdxId(String), - /// A URL to a custom license - Link(Url), + /// An [SPDX identifier](https://spdx.org/licenses/) + SpdxId(String), + /// A URL to a custom license + Link(Url), } /// Additional user defined metadata, can be any valid json. #[derive(Debug, Serialize)] pub struct InkProjectUser { - #[serde(flatten)] - json: Map, + #[serde(flatten)] + json: Map, } impl InkProjectUser { - /// Constructs a new InkProjectUser - pub fn new(json: Map) -> Self { - InkProjectUser { json } - } - - pub fn from_str(json: &str) -> serde_json::Result { - serde_json::from_str(json.as_ref()).map(Self::new) - } + /// Constructs a new InkProjectUser + pub fn new(json: Map) -> Self { + InkProjectUser { json } + } + + pub fn from_str(json: &str) -> serde_json::Result { + serde_json::from_str(json.as_ref()).map(Self::new) + } } /// Type state for builders to tell that some mandatory state has not yet been set @@ -247,17 +234,17 @@ impl InkProjectUser { pub struct Missing(PhantomData S>); mod state { - //! Type states that tell what state of the project metadata has not - //! yet been set properly for a valid construction. + //! Type states that tell what state of the project metadata has not + //! yet been set properly for a valid construction. - /// Type state for the name of the project. - pub struct Name; + /// Type state for the name of the project. + pub struct Name; - /// Type state for the version of the project. - pub struct Version; + /// Type state for the version of the project. + pub struct Version; - /// Type state for the authors of the project. - pub struct Authors; + /// Type state for the authors of the project. + pub struct Authors; } /// Build an [`InkProjectContract`], ensuring required fields are supplied @@ -290,114 +277,110 @@ mod state { /// ``` #[allow(clippy::type_complexity)] pub struct InkProjectContractBuilder { - contract: InkProjectContract, - marker: PhantomData (Name, Version, Authors)>, + contract: InkProjectContract, + marker: PhantomData (Name, Version, Authors)>, } impl InkProjectContractBuilder, V, A> { - /// Set the contract name (required) - pub fn name(self, name: S) -> InkProjectContractBuilder - where - S: AsRef, - { - InkProjectContractBuilder { - contract: InkProjectContract { - name: name.as_ref().to_owned(), - ..self.contract - }, - marker: PhantomData, - } - } + /// Set the contract name (required) + pub fn name(self, name: S) -> InkProjectContractBuilder + where + S: AsRef, + { + InkProjectContractBuilder { + contract: InkProjectContract { + name: name.as_ref().to_owned(), + ..self.contract + }, + marker: PhantomData, + } + } } impl InkProjectContractBuilder, A> { - /// Set the contract version (required) - pub fn version(self, version: Version) -> InkProjectContractBuilder { - InkProjectContractBuilder { - contract: InkProjectContract { - version, - ..self.contract - }, - marker: PhantomData, - } - } + /// Set the contract version (required) + pub fn version(self, version: Version) -> InkProjectContractBuilder { + InkProjectContractBuilder { + contract: InkProjectContract { + version, + ..self.contract + }, + marker: PhantomData, + } + } } impl InkProjectContractBuilder> { - /// Set the contract authors (required) - pub fn authors( - self, - authors: I, - ) -> InkProjectContractBuilder - where - I: IntoIterator, - S: AsRef, - { - InkProjectContractBuilder { - contract: InkProjectContract { - authors: authors.into_iter().map(|s| s.as_ref().into()).collect(), - ..self.contract - }, - marker: PhantomData, - } - } + /// Set the contract authors (required) + pub fn authors(self, authors: I) -> InkProjectContractBuilder + where + I: IntoIterator, + S: AsRef, + { + InkProjectContractBuilder { + contract: InkProjectContract { + authors: authors.into_iter().map(|s| s.as_ref().into()).collect(), + ..self.contract + }, + marker: PhantomData, + } + } } impl InkProjectContractBuilder { - /// Set the contract description (optional) - pub fn description(mut self, description: S) -> Self - where - S: AsRef, - { - self.contract.description = Some(description.as_ref().to_owned()); - self - } - - /// Set the contract documentation url (optional) - pub fn documentation(mut self, documentation: Url) -> Self - { - self.contract.documentation = Some(documentation); - self - } - - /// Set the contract documentation url (optional) - pub fn repository(mut self, repository: Url) -> Self { - self.contract.repository = Some(repository); - self - } - - /// Set the contract homepage url (optional) - pub fn homepage(mut self, homepage: Url) -> Self { - self.contract.homepage = Some(homepage.into()); - self - } - - /// Set the contract license (optional) - pub fn license(mut self, license: License) -> Self { - self.contract.license = Some(license); - self - } + /// Set the contract description (optional) + pub fn description(mut self, description: S) -> Self + where + S: AsRef, + { + self.contract.description = Some(description.as_ref().to_owned()); + self + } + + /// Set the contract documentation url (optional) + pub fn documentation(mut self, documentation: Url) -> Self { + self.contract.documentation = Some(documentation); + self + } + + /// Set the contract documentation url (optional) + pub fn repository(mut self, repository: Url) -> Self { + self.contract.repository = Some(repository); + self + } + + /// Set the contract homepage url (optional) + pub fn homepage(mut self, homepage: Url) -> Self { + self.contract.homepage = Some(homepage.into()); + self + } + + /// Set the contract license (optional) + pub fn license(mut self, license: License) -> Self { + self.contract.license = Some(license); + self + } } impl InkProjectContractBuilder { - pub fn done(self) -> InkProjectContract { - self.contract - } + pub fn done(self) -> InkProjectContract { + self.contract + } } /// Serializes the given bytes as byte string. fn serialize_as_byte_str(bytes: &[u8], serializer: S) -> Result - where - S: serde::Serializer, +where + S: serde::Serializer, { - if bytes.is_empty() { - // Return empty string without prepended `0x`. - return serializer.serialize_str("") - } - let mut hex = String::with_capacity(bytes.len() * 2 + 2); - write!(hex, "0x").expect("failed writing to string"); - for byte in bytes { - write!(hex, "{:02x}", byte).expect("failed writing to string"); - } - serializer.serialize_str(&hex) + if bytes.is_empty() { + // Return empty string without prepended `0x`. + return serializer.serialize_str(""); + } + let mut hex = String::with_capacity(bytes.len() * 2 + 2); + write!(hex, "0x").expect("failed writing to string"); + for byte in bytes { + write!(hex, "{:02x}", byte).expect("failed writing to string"); + } + serializer.serialize_str(&hex) } From 3f600e538a6ea0d8e06bfaba811093b93e6ca6d1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 13:34:08 +0100 Subject: [PATCH 12/52] Capture ink metadata from stdout --- src/cmd/metadata/mod.rs | 10 ++++++++-- src/util.rs | 19 +++++++++++++------ templates/tools/generate-metadata/main.rs | 9 ++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index fff98579a..503ef24f6 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -22,6 +22,7 @@ use crate::{ UnstableFlags, Verbosity, }; use anyhow::Result; +use std::fs; const METADATA_FILE: &str = "metadata.json"; @@ -45,7 +46,7 @@ pub(crate) fn execute_generate_metadata( let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { let target_dir_arg = format!("--target-dir={}", target_dir.to_string_lossy()); - util::invoke_cargo( + let stdout = util::invoke_cargo( "run", &[ "--package", @@ -57,7 +58,12 @@ pub(crate) fn execute_generate_metadata( ], original_manifest_path.directory(), verbosity, - ) + )?; + + let metadata_json: serde_json::Map = serde_json::from_slice(&stdout)?; + let contents = serde_json::to_string_pretty(&metadata_json)?; + fs::write(&out_path, contents)?; + Ok(()) }; if unstable_options.original_manifest { diff --git a/src/util.rs b/src/util.rs index 3a6556e76..51bca154c 100644 --- a/src/util.rs +++ b/src/util.rs @@ -53,12 +53,14 @@ pub fn assert_channel() -> Result<()> { } /// Run cargo with the supplied args +/// +/// If successful, returns the stdout bytes pub(crate) fn invoke_cargo( command: &str, args: I, working_dir: Option

, verbosity: Option, -) -> Result<()> +) -> Result> where I: IntoIterator + std::fmt::Debug, S: AsRef, @@ -79,14 +81,19 @@ where None => &mut cmd, }; - let status = cmd - .status() + log::info!("invoking cargo: {:?}", cmd); + + let child = cmd + // capture the stdout for the metadata JSON result + .stdout(std::process::Stdio::piped()) + .spawn() .context(format!("Error executing `{:?}`", cmd))?; + let output = child.wait_with_output()?; - if status.success() { - Ok(()) + if output.status.success() { + Ok(output.stdout) } else { - anyhow::bail!("`{:?}` failed with exit code: {:?}", cmd, status.code()); + anyhow::bail!("`{:?}` failed with exit code: {:?}", cmd, output.status.code()); } } diff --git a/templates/tools/generate-metadata/main.rs b/templates/tools/generate-metadata/main.rs index 3d5eeb233..66ecdde26 100644 --- a/templates/tools/generate-metadata/main.rs +++ b/templates/tools/generate-metadata/main.rs @@ -1,13 +1,12 @@ extern crate contract; extern "Rust" { - fn __ink_generate_metadata() -> ink_abi::InkProject; + fn __ink_generate_metadata() -> ink_metadata::InkProject; } fn main() -> Result<(), std::io::Error> { - let ink_project = unsafe { __ink_generate_metadata() }; - let contents = serde_json::to_string_pretty(&ink_project)?; - std::fs::create_dir("target").ok(); - std::fs::write("target/metadata.json", contents)?; + let metadata = unsafe { __ink_generate_metadata() }; + let contents = serde_json::to_string_pretty(&metadata)?; + print!("{}", contents); Ok(()) } From 532785253efcc91b4b55d26041c3a23d698063d8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 15:06:00 +0100 Subject: [PATCH 13/52] Update comment --- src/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.rs b/src/util.rs index 51bca154c..c650b0688 100644 --- a/src/util.rs +++ b/src/util.rs @@ -84,7 +84,7 @@ where log::info!("invoking cargo: {:?}", cmd); let child = cmd - // capture the stdout for the metadata JSON result + // capture the stdout to return from this function as bytes .stdout(std::process::Stdio::piped()) .spawn() .context(format!("Error executing `{:?}`", cmd))?; From 88f3624734fc75e3caabe7e1716572d8b9836f3c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 15:44:28 +0100 Subject: [PATCH 14/52] Construct hardcoded metadata values --- src/cmd/metadata/contract.rs | 66 ++++++++++++++++++------------------ src/cmd/metadata/mod.rs | 63 ++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 83ec96e07..9b40c0905 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -29,10 +29,10 @@ const METADATA_VERSION: &str = "0.1.0"; #[derive(Debug, Serialize)] pub struct ContractMetadata { metadata_version: semver::Version, - source: InkProjectSource, - contract: InkProjectContract, + source: Source, + contract: Contract, #[serde(skip_serializing_if = "Option::is_none")] - user: Option, + user: Option, /// Raw JSON of the metadata generated by the ink! contract itself #[serde(flatten)] ink: Map, @@ -41,9 +41,9 @@ pub struct ContractMetadata { impl ContractMetadata { /// Construct a new ContractMetadata pub fn new( - source: InkProjectSource, - contract: InkProjectContract, - user: Option, + source: Source, + contract: Contract, + user: Option, ink: Map, ) -> Self { let metadata_version = semver::Version::parse(METADATA_VERSION) @@ -60,17 +60,17 @@ impl ContractMetadata { } #[derive(Debug, Serialize)] -pub struct InkProjectSource { +pub struct Source { #[serde(serialize_with = "serialize_as_byte_str")] hash: [u8; 32], language: SourceLanguage, compiler: SourceCompiler, } -impl InkProjectSource { +impl Source { /// Constructs a new InkProjectSource. pub fn new(hash: [u8; 32], language: SourceLanguage, compiler: SourceCompiler) -> Self { - InkProjectSource { + Source { hash, language, compiler, @@ -163,7 +163,7 @@ impl Display for Compiler { /// Metadata about a smart contract. #[derive(Debug, Serialize)] -pub struct InkProjectContract { +pub struct Contract { name: String, version: Version, authors: Vec, @@ -179,14 +179,14 @@ pub struct InkProjectContract { license: Option, } -impl InkProjectContract { +impl Contract { /// Constructs a new InkProjectContractBuilder. - pub fn build() -> InkProjectContractBuilder< + pub fn build() -> ContractBuilder< Missing, Missing, Missing, > { - InkProjectContractBuilder { + ContractBuilder { contract: Self { name: Default::default(), version: Version::new(0, 0, 0), @@ -213,15 +213,15 @@ pub enum License { /// Additional user defined metadata, can be any valid json. #[derive(Debug, Serialize)] -pub struct InkProjectUser { +pub struct User { #[serde(flatten)] json: Map, } -impl InkProjectUser { +impl User { /// Constructs a new InkProjectUser pub fn new(json: Map) -> Self { - InkProjectUser { json } + User { json } } pub fn from_str(json: &str) -> serde_json::Result { @@ -276,19 +276,19 @@ mod state { /// .done(); /// ``` #[allow(clippy::type_complexity)] -pub struct InkProjectContractBuilder { - contract: InkProjectContract, +pub struct ContractBuilder { + contract: Contract, marker: PhantomData (Name, Version, Authors)>, } -impl InkProjectContractBuilder, V, A> { +impl ContractBuilder, V, A> { /// Set the contract name (required) - pub fn name(self, name: S) -> InkProjectContractBuilder + pub fn name(self, name: S) -> ContractBuilder where S: AsRef, { - InkProjectContractBuilder { - contract: InkProjectContract { + ContractBuilder { + contract: Contract { name: name.as_ref().to_owned(), ..self.contract }, @@ -297,11 +297,11 @@ impl InkProjectContractBuilder, V, A> { } } -impl InkProjectContractBuilder, A> { +impl ContractBuilder, A> { /// Set the contract version (required) - pub fn version(self, version: Version) -> InkProjectContractBuilder { - InkProjectContractBuilder { - contract: InkProjectContract { + pub fn version(self, version: Version) -> ContractBuilder { + ContractBuilder { + contract: Contract { version, ..self.contract }, @@ -310,15 +310,15 @@ impl InkProjectContractBuilder, A> { } } -impl InkProjectContractBuilder> { +impl ContractBuilder> { /// Set the contract authors (required) - pub fn authors(self, authors: I) -> InkProjectContractBuilder + pub fn authors(self, authors: I) -> ContractBuilder where I: IntoIterator, S: AsRef, { - InkProjectContractBuilder { - contract: InkProjectContract { + ContractBuilder { + contract: Contract { authors: authors.into_iter().map(|s| s.as_ref().into()).collect(), ..self.contract }, @@ -327,7 +327,7 @@ impl InkProjectContractBuilder> { } } -impl InkProjectContractBuilder { +impl ContractBuilder { /// Set the contract description (optional) pub fn description(mut self, description: S) -> Self where @@ -362,8 +362,8 @@ impl InkProjectContractBuilder { } } -impl InkProjectContractBuilder { - pub fn done(self) -> InkProjectContract { +impl ContractBuilder { + pub fn done(self) -> Contract { self.contract } } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 503ef24f6..c4e8225bd 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -16,13 +16,27 @@ mod contract; +use contract::{ + Compiler, + ContractMetadata, + Source, + SourceCompiler, + SourceLanguage, + Language, + License, + Contract, + ContractBuilder, + User, +}; use crate::{ util, workspace::{ManifestPath, Workspace}, UnstableFlags, Verbosity, }; use anyhow::Result; +use serde_json::{Map, Value}; use std::fs; +use semver::Version; const METADATA_FILE: &str = "metadata.json"; @@ -86,6 +100,55 @@ pub(crate) fn execute_generate_metadata( )) } +fn construct_metadata(ink_metadata: Map) -> Result { + // todo: generate these params + let hash = [0u8; 32]; + let ink_version = Version::new(2, 1, 0); + let rust_version = Version::new(1, 41, 0); + let contract_name = "test"; + let contract_version = Version::new(0, 0, 0); + let contract_authors = vec!["author@example.com"]; + // optional + let description: Option<&str> = None; + let documentation = None; + let repository = None; + let homepage = None; + let license = None; + + let source = { + let lang = SourceLanguage::new(Language::Ink, ink_version); + let compiler = SourceCompiler::new(Compiler::RustC, rust_version); + Source::new(hash, lang, compiler) + }; + + // Required contract fields + let contract = Contract::build() + .name(contract_name) + .version(contract_version) + .authors(contract_authors); + + // Optional fields + if let Some(description) = description { + contract.description(description); + } + if let Some(documentation) = documentation { + contract.documentation(documentation); + } + if let Some(repository) = repository { + contract.repository(repository); + } + if let Some(homepage) = homepage { + contract.homepage(homepage); + } + if let Some(license) = license { + contract.license(license); + } + + let user: Option = None; + + Ok(ContractMetadata::new(source, contract.done(), user, ink_metadata)) +} + #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { From d590c810882b1dcd3bd47c3958d2ca497b94af91 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Wed, 8 Jul 2020 16:03:53 +0100 Subject: [PATCH 15/52] Remove contract metadata builder, it is redundant --- src/cmd/metadata/contract.rs | 182 ++++------------------------------- src/cmd/metadata/mod.rs | 65 +++++-------- src/util.rs | 6 +- 3 files changed, 50 insertions(+), 203 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 9b40c0905..8243ab3dd 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -14,10 +14,7 @@ // You should have received a copy of the GNU General Public License // along with ink!. If not, see . -use core::{ - fmt::{Display, Formatter, Result as DisplayResult, Write}, - marker::PhantomData, -}; +use core::fmt::{Display, Formatter, Result as DisplayResult, Write}; use semver::Version; use serde::{Serialize, Serializer}; use serde_json::{Map, Value}; @@ -180,24 +177,26 @@ pub struct Contract { } impl Contract { - /// Constructs a new InkProjectContractBuilder. - pub fn build() -> ContractBuilder< - Missing, - Missing, - Missing, - > { - ContractBuilder { - contract: Self { - name: Default::default(), - version: Version::new(0, 0, 0), - authors: vec![], - description: None, - documentation: None, - repository: None, - homepage: None, - license: None, - }, - marker: Default::default(), + /// Constructs a new Contract. + pub fn new( + name: String, + version: Version, + authors: Vec, + description: Option, + documentation: Option, + repository: Option, + homepage: Option, + license: Option, + ) -> Self { + Contract { + name, + version, + authors, + description, + documentation, + repository, + homepage, + license, } } } @@ -229,145 +228,6 @@ impl User { } } -/// Type state for builders to tell that some mandatory state has not yet been set -/// yet or to fail upon setting the same state multiple times. -pub struct Missing(PhantomData S>); - -mod state { - //! Type states that tell what state of the project metadata has not - //! yet been set properly for a valid construction. - - /// Type state for the name of the project. - pub struct Name; - - /// Type state for the version of the project. - pub struct Version; - - /// Type state for the authors of the project. - pub struct Authors; -} - -/// Build an [`InkProjectContract`], ensuring required fields are supplied -/// -/// # Example -/// -/// ``` -/// # use crate::ink_metadata::InkProjectContract; -/// # use semver::Version; -/// # use url::Url; -/// // contract metadata with the minimum set of required fields -/// let metadata1: InkProjectContract = -/// InkProjectContract::build() -/// .name("example") -/// .version(Version::new(0, 1, 0)) -/// .authors(vec!["author@example.com"]) -/// .done(); -/// -/// // contract metadata with optional fields -/// let metadata2: InkProjectContract = -/// InkProjectContract::build() -/// .name("example") -/// .version(Version::new(0, 1, 0)) -/// .authors(vec!["author@example.com"]) -/// .description("description") -/// .documentation(Url::parse("http://example.com").unwrap()) -/// .repository(Url::parse("http://example.com").unwrap()) -/// .homepage(Url::parse("http://example.com").unwrap()) -/// .done(); -/// ``` -#[allow(clippy::type_complexity)] -pub struct ContractBuilder { - contract: Contract, - marker: PhantomData (Name, Version, Authors)>, -} - -impl ContractBuilder, V, A> { - /// Set the contract name (required) - pub fn name(self, name: S) -> ContractBuilder - where - S: AsRef, - { - ContractBuilder { - contract: Contract { - name: name.as_ref().to_owned(), - ..self.contract - }, - marker: PhantomData, - } - } -} - -impl ContractBuilder, A> { - /// Set the contract version (required) - pub fn version(self, version: Version) -> ContractBuilder { - ContractBuilder { - contract: Contract { - version, - ..self.contract - }, - marker: PhantomData, - } - } -} - -impl ContractBuilder> { - /// Set the contract authors (required) - pub fn authors(self, authors: I) -> ContractBuilder - where - I: IntoIterator, - S: AsRef, - { - ContractBuilder { - contract: Contract { - authors: authors.into_iter().map(|s| s.as_ref().into()).collect(), - ..self.contract - }, - marker: PhantomData, - } - } -} - -impl ContractBuilder { - /// Set the contract description (optional) - pub fn description(mut self, description: S) -> Self - where - S: AsRef, - { - self.contract.description = Some(description.as_ref().to_owned()); - self - } - - /// Set the contract documentation url (optional) - pub fn documentation(mut self, documentation: Url) -> Self { - self.contract.documentation = Some(documentation); - self - } - - /// Set the contract documentation url (optional) - pub fn repository(mut self, repository: Url) -> Self { - self.contract.repository = Some(repository); - self - } - - /// Set the contract homepage url (optional) - pub fn homepage(mut self, homepage: Url) -> Self { - self.contract.homepage = Some(homepage.into()); - self - } - - /// Set the contract license (optional) - pub fn license(mut self, license: License) -> Self { - self.contract.license = Some(license); - self - } -} - -impl ContractBuilder { - pub fn done(self) -> Contract { - self.contract - } -} - /// Serializes the given bytes as byte string. fn serialize_as_byte_str(bytes: &[u8], serializer: S) -> Result where diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index c4e8225bd..be63598d0 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -16,27 +16,19 @@ mod contract; -use contract::{ - Compiler, - ContractMetadata, - Source, - SourceCompiler, - SourceLanguage, - Language, - License, - Contract, - ContractBuilder, - User, -}; use crate::{ util, workspace::{ManifestPath, Workspace}, UnstableFlags, Verbosity, }; use anyhow::Result; +use contract::{ + Compiler, Contract, ContractMetadata, Language, License, Source, SourceCompiler, + SourceLanguage, User, +}; +use semver::Version; use serde_json::{Map, Value}; use std::fs; -use semver::Version; const METADATA_FILE: &str = "metadata.json"; @@ -74,8 +66,10 @@ pub(crate) fn execute_generate_metadata( verbosity, )?; - let metadata_json: serde_json::Map = serde_json::from_slice(&stdout)?; - let contents = serde_json::to_string_pretty(&metadata_json)?; + let ink_metadata: serde_json::Map = + serde_json::from_slice(&stdout)?; + let metadata = construct_metadata(ink_metadata)?; + let contents = serde_json::to_string_pretty(&metadata)?; fs::write(&out_path, contents)?; Ok(()) }; @@ -105,15 +99,15 @@ fn construct_metadata(ink_metadata: Map) -> Result = None; + let description: Option = None; let documentation = None; let repository = None; let homepage = None; - let license = None; + let license: Option = None; let source = { let lang = SourceLanguage::new(Language::Ink, ink_version); @@ -122,31 +116,20 @@ fn construct_metadata(ink_metadata: Map) -> Result = None; - Ok(ContractMetadata::new(source, contract.done(), user, ink_metadata)) + Ok(ContractMetadata::new(source, contract, user, ink_metadata)) } #[cfg(feature = "test-ci-only")] diff --git a/src/util.rs b/src/util.rs index c650b0688..1260393c5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -93,7 +93,11 @@ where if output.status.success() { Ok(output.stdout) } else { - anyhow::bail!("`{:?}` failed with exit code: {:?}", cmd, output.status.code()); + anyhow::bail!( + "`{:?}` failed with exit code: {:?}", + cmd, + output.status.code() + ); } } From 94ad425cbd9a25eddd1d2af69f73a01d1dc5c897 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 11:10:28 +0100 Subject: [PATCH 16/52] Generate contract wasm hash --- Cargo.lock | 71 ++++++++++--- Cargo.toml | 1 + src/cmd/build.rs | 10 +- src/cmd/metadata/mod.rs | 222 +++++++++++++++++++++++----------------- src/main.rs | 19 ++-- 5 files changed, 206 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38908c47a..8d358f37e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,19 @@ dependencies = [ "radium", ] +[[package]] +name = "blake2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ce5b6108f8e154604bd4eb76a2f726066c3464d5a552a4229262a18c9bb471" +dependencies = [ + "byte-tools", + "byteorder", + "crypto-mac 0.8.0", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "blake2-rfc" version = "0.2.18" @@ -272,7 +285,7 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", ] [[package]] @@ -350,6 +363,7 @@ dependencies = [ "anyhow", "assert_matches", "async-std", + "blake2", "cargo-xbuild", "cargo_metadata", "colored", @@ -568,10 +582,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array", + "generic-array 0.12.3", "subtle 1.0.0", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.2", + "subtle 2.2.2", +] + [[package]] name = "ctor" version = "0.1.15" @@ -589,7 +613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" dependencies = [ "byteorder", - "digest", + "digest 0.8.1", "rand_core 0.5.1", "subtle 2.2.2", "zeroize", @@ -624,7 +648,16 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.2", ] [[package]] @@ -972,6 +1005,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -1092,8 +1135,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" dependencies = [ - "crypto-mac", - "digest", + "crypto-mac 0.7.0", + "digest 0.8.1", ] [[package]] @@ -1102,8 +1145,8 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" dependencies = [ - "digest", - "generic-array", + "digest 0.8.1", + "generic-array 0.12.3", "hmac", ] @@ -1507,7 +1550,7 @@ checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" dependencies = [ "arrayref", "crunchy", - "digest", + "digest 0.8.1", "hmac-drbg", "rand 0.7.3", "sha2", @@ -1649,7 +1692,7 @@ checksum = "f75db05d738947aa5389863aadafbcf2e509d7ba099dc2ddcdf4fc66bf7a9e03" dependencies = [ "blake2b_simd", "blake2s_simd", - "digest", + "digest 0.8.1", "sha-1", "sha2", "sha3", @@ -1945,7 +1988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" dependencies = [ "byteorder", - "crypto-mac", + "crypto-mac 0.7.0", ] [[package]] @@ -2548,7 +2591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ "block-buffer", - "digest", + "digest 0.8.1", "fake-simd", "opaque-debug", ] @@ -2566,7 +2609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" dependencies = [ "block-buffer", - "digest", + "digest 0.8.1", "fake-simd", "opaque-debug", ] @@ -2579,7 +2622,7 @@ checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" dependencies = [ "block-buffer", "byte-tools", - "digest", + "digest 0.8.1", "keccak", "opaque-debug", ] diff --git a/Cargo.toml b/Cargo.toml index 2d263d69a..1e2dba657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ colored = "1.9" toml = "0.5.4" cargo-xbuild = "0.5.32" rustc_version = "0.2.3" +blake2 = "0.9.0" semver = { version = "0.10.0", features = ["serde"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = "1.0" diff --git a/src/cmd/build.rs b/src/cmd/build.rs index a9f34bbb4..0e028e636 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -169,6 +169,9 @@ fn build_cargo_project( .using_temp(xbuild)?; } + // clear RUSTFLAGS + std::env::remove_var("RUSTFLAGS"); + Ok(()) } @@ -298,7 +301,7 @@ pub(crate) fn execute_build( manifest_path: ManifestPath, verbosity: Option, unstable_options: UnstableFlags, -) -> Result { +) -> Result { println!( " {} {}", "[1/4]".bold(), @@ -324,10 +327,7 @@ pub(crate) fn execute_build( ); optimize_wasm(&crate_metadata)?; - Ok(format!( - "\nYour contract is ready. You can find it here:\n{}", - crate_metadata.dest_wasm.display().to_string().bold() - )) + Ok(crate_metadata.dest_wasm) } #[cfg(feature = "test-ci-only")] diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index be63598d0..4a26550b5 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -16,6 +16,7 @@ mod contract; +use cargo_metadata::Metadata; use crate::{ util, workspace::{ManifestPath, Workspace}, @@ -27,109 +28,146 @@ use contract::{ SourceLanguage, User, }; use semver::Version; -use serde_json::{Map, Value}; use std::fs; const METADATA_FILE: &str = "metadata.json"; -/// Generates a file with metadata describing the ABI of the smart-contract. -/// -/// It does so by generating and invoking a temporary workspace member. -pub(crate) fn execute_generate_metadata( - original_manifest_path: ManifestPath, +/// Executes the metadata generation process +struct GenerateMetadataCommand { + manifest_path: ManifestPath, verbosity: Option, unstable_options: UnstableFlags, -) -> Result { - util::assert_channel()?; - println!(" Generating metadata"); - - let (metadata, root_package_id) = crate::util::get_cargo_metadata(&original_manifest_path)?; - - let out_path = metadata.target_directory.join(METADATA_FILE); - let out_path_display = format!("{}", out_path.display()); - - let target_dir = metadata.target_directory.clone(); - - let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { - let target_dir_arg = format!("--target-dir={}", target_dir.to_string_lossy()); - let stdout = util::invoke_cargo( - "run", - &[ - "--package", - "metadata-gen", - &manifest_path.cargo_arg(), - &target_dir_arg, - "--release", - // "--no-default-features", // Breaks builds for MacOS (linker errors), we should investigate this issue asap! - ], - original_manifest_path.directory(), - verbosity, - )?; - - let ink_metadata: serde_json::Map = - serde_json::from_slice(&stdout)?; - let metadata = construct_metadata(ink_metadata)?; - let contents = serde_json::to_string_pretty(&metadata)?; - fs::write(&out_path, contents)?; - Ok(()) - }; +} - if unstable_options.original_manifest { - generate_metadata(&original_manifest_path)?; - } else { - Workspace::new(&metadata, &root_package_id)? - .with_root_package_manifest(|manifest| { - manifest - .with_added_crate_type("rlib")? - .with_profile_release_lto(false)?; - Ok(()) - })? - .with_metadata_gen_package()? - .using_temp(generate_metadata)?; +impl GenerateMetadataCommand { + pub fn exec(&self)-> Result { + util::assert_channel()?; + println!(" Generating metadata"); + + let (cargo_meta, root_package_id) = crate::util::get_cargo_metadata(&self.manifest_path)?; + + let out_path = cargo_meta.target_directory.join(METADATA_FILE); + let out_path_display = format!("{}", out_path.display()); + + let target_dir = cargo_meta.target_directory.clone(); + + // build the extended contract project metadata + let (source_meta, contract_meta, user_meta) = self.extended_metadata(&cargo_meta)?; + + let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { + let target_dir_arg = format!("--target-dir={}", target_dir.to_string_lossy()); + let stdout = util::invoke_cargo( + "run", + &[ + "--package", + "metadata-gen", + &manifest_path.cargo_arg(), + &target_dir_arg, + "--release", + // "--no-default-features", // Breaks builds for MacOS (linker errors), we should investigate this issue asap! + ], + self.manifest_path.directory(), + self.verbosity, + )?; + + let ink_meta: serde_json::Map = + serde_json::from_slice(&stdout)?; + let metadata = ContractMetadata::new(source_meta, contract_meta, user_meta, ink_meta); + let contents = serde_json::to_string_pretty(&metadata)?; + fs::write(&out_path, contents)?; + Ok(()) + }; + + if self.unstable_options.original_manifest { + generate_metadata(&self.manifest_path)?; + } else { + Workspace::new(&cargo_meta, &root_package_id)? + .with_root_package_manifest(|manifest| { + manifest + .with_added_crate_type("rlib")? + .with_profile_release_lto(false)?; + Ok(()) + })? + .with_metadata_gen_package()? + .using_temp(generate_metadata)?; + } + + Ok(format!( + "Your metadata file is ready.\nYou can find it here:\n{}", + out_path_display + )) } - Ok(format!( - "Your metadata file is ready.\nYou can find it here:\n{}", - out_path_display - )) -} + /// Generate the extended contract project metadata + fn extended_metadata(&self, cargo_meta: &Metadata) -> Result<(Source, Contract, Option)> { + // todo: generate these params + let ink_version = Version::new(2, 1, 0); + let rust_version = Version::new(1, 41, 0); + let contract_name = "test".to_string(); + let contract_version = Version::new(0, 0, 0); + let contract_authors = vec!["author@example.com".to_string()]; + // optional + let description: Option = None; + let documentation = None; + let repository = None; + let homepage = None; + let license: Option = None; + + let hash = self.wasm_hash()?; + + let source = { + let lang = SourceLanguage::new(Language::Ink, ink_version); + let compiler = SourceCompiler::new(Compiler::RustC, rust_version); + Source::new(hash, lang, compiler) + }; + + // Required contract fields + let contract = Contract::new( + contract_name, + contract_version, + contract_authors, + description, + documentation, + repository, + homepage, + license, + ); + + let user: Option = None; + + Ok((source, contract, user)) + } -fn construct_metadata(ink_metadata: Map) -> Result { - // todo: generate these params - let hash = [0u8; 32]; - let ink_version = Version::new(2, 1, 0); - let rust_version = Version::new(1, 41, 0); - let contract_name = "test".to_string(); - let contract_version = Version::new(0, 0, 0); - let contract_authors = vec!["author@example.com".to_string()]; - // optional - let description: Option = None; - let documentation = None; - let repository = None; - let homepage = None; - let license: Option = None; - - let source = { - let lang = SourceLanguage::new(Language::Ink, ink_version); - let compiler = SourceCompiler::new(Compiler::RustC, rust_version); - Source::new(hash, lang, compiler) - }; + /// Compile the contract and then hash the resulting wasm + fn wasm_hash(&self) -> Result<[u8; 32]> { + let wasm_path = super::execute_build(self.manifest_path.clone(), self.verbosity, self.unstable_options.clone())?; + let wasm = fs::read(wasm_path)?; + + use ::blake2::digest::{ + Update as _, + VariableOutput as _, + }; + let mut output = [0u8; 32]; + let mut blake2 = blake2::VarBlake2b::new_keyed(&[], 32); + blake2.update(wasm); + blake2.finalize_variable(|result| output.copy_from_slice(result)); + Ok(output) + } +} - // Required contract fields - let contract = Contract::new( - contract_name, - contract_version, - contract_authors, - description, - documentation, - repository, - homepage, - license, - ); - - let user: Option = None; - - Ok(ContractMetadata::new(source, contract, user, ink_metadata)) +/// Generates a file with metadata describing the ABI of the smart-contract. +/// +/// It does so by generating and invoking a temporary workspace member. +pub(crate) fn execute_generate_metadata( + manifest_path: ManifestPath, + verbosity: Option, + unstable_options: UnstableFlags, +) -> Result { + GenerateMetadataCommand { + manifest_path, + verbosity, + unstable_options, + }.exec() } #[cfg(feature = "test-ci-only")] diff --git a/src/main.rs b/src/main.rs index 787a6f8d3..9f14c4bc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,6 +98,7 @@ struct VerbosityFlags { verbose: bool, } +#[derive(Clone, Copy)] enum Verbosity { Quiet, Verbose, @@ -123,7 +124,7 @@ struct UnstableOptions { options: Vec, } -#[derive(Default)] +#[derive(Clone, Default)] struct UnstableFlags { original_manifest: bool, } @@ -239,11 +240,17 @@ fn exec(cmd: Command) -> Result { Command::Build { verbosity, unstable_options, - } => cmd::execute_build( - Default::default(), - verbosity.try_into()?, - unstable_options.try_into()?, - ), + } => { + let dest_wasm = cmd::execute_build( + Default::default(), + verbosity.try_into()?, + unstable_options.try_into()?, + )?; + Ok(format!( + "\nYour contract is ready. You can find it here:\n{}", + dest_wasm.display().to_string().bold() + )) + }, Command::GenerateMetadata { verbosity, unstable_options, From 50355c79352b7bead5c7bc8617b6a84a5c4f02ae Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 13:48:15 +0100 Subject: [PATCH 17/52] Refactor: extract CrateMetadata for use in both metadata and build --- src/cmd/build.rs | 78 ++++-------------------------------- src/cmd/deploy.rs | 7 +++- src/cmd/metadata/mod.rs | 30 ++++++++------ src/crate_metadata.rs | 89 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 8 ++-- src/util.rs | 16 -------- 6 files changed, 125 insertions(+), 103 deletions(-) create mode 100644 src/crate_metadata.rs diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 0e028e636..072cde4a5 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -22,6 +22,7 @@ use std::{ }; use crate::{ + crate_metadata::CrateMetadata, util, workspace::{ManifestPath, Profile, Workspace}, UnstableFlags, Verbosity, @@ -34,62 +35,6 @@ use parity_wasm::elements::{External, MemoryType, Module, Section}; /// This is the maximum number of pages available for a contract to allocate. const MAX_MEMORY_PAGES: u32 = 16; -/// Relevant metadata obtained from Cargo.toml. -#[derive(Debug)] -pub struct CrateMetadata { - manifest_path: ManifestPath, - cargo_meta: cargo_metadata::Metadata, - package_name: String, - root_package: Package, - original_wasm: PathBuf, - pub dest_wasm: PathBuf, -} - -impl CrateMetadata { - pub fn target_dir(&self) -> &Path { - self.cargo_meta.target_directory.as_path() - } -} - -/// Parses the contract manifest and returns relevant metadata. -pub fn collect_crate_metadata(manifest_path: &ManifestPath) -> Result { - let (metadata, root_package_id) = crate::util::get_cargo_metadata(manifest_path)?; - - // Find the root package by id in the list of packages. It is logical error if the root - // package is not found in the list. - let root_package = metadata - .packages - .iter() - .find(|package| package.id == root_package_id) - .expect("The package is not found in the `cargo metadata` output") - .clone(); - - // Normalize the package name. - let package_name = root_package.name.replace("-", "_"); - - // {target_dir}/wasm32-unknown-unknown/release/{package_name}.wasm - let mut original_wasm = metadata.target_directory.clone(); - original_wasm.push("wasm32-unknown-unknown"); - original_wasm.push("release"); - original_wasm.push(package_name.clone()); - original_wasm.set_extension("wasm"); - - // {target_dir}/{package_name}.wasm - let mut dest_wasm = metadata.target_directory.clone(); - dest_wasm.push(package_name.clone()); - dest_wasm.set_extension("wasm"); - - let crate_metadata = CrateMetadata { - manifest_path: manifest_path.clone(), - cargo_meta: metadata, - root_package: root_package.clone(), - package_name, - original_wasm, - dest_wasm, - }; - Ok(crate_metadata) -} - /// Builds the project in the specified directory, defaults to the current directory. /// /// Uses [`cargo-xbuild`](https://github.com/rust-osdev/cargo-xbuild) for maximum optimization of @@ -125,7 +70,7 @@ fn build_cargo_project( let xbuild = |manifest_path: &ManifestPath| { let manifest_path = Some(manifest_path); let target = Some("wasm32-unknown-unknown"); - let target_dir = crate_metadata.target_dir(); + let target_dir = &crate_metadata.cargo_meta.target_directory; let other_args = [ "--no-default-features", "--release", @@ -298,36 +243,29 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> { /// /// It does so by invoking build by cargo and then post processing the final binary. pub(crate) fn execute_build( - manifest_path: ManifestPath, + crate_metadata: &CrateMetadata, verbosity: Option, unstable_options: UnstableFlags, -) -> Result { - println!( - " {} {}", - "[1/4]".bold(), - "Collecting crate metadata".bright_green().bold() - ); - let crate_metadata = collect_crate_metadata(&manifest_path)?; +) -> Result<()> { println!( " {} {}", - "[2/4]".bold(), + "[1/3]".bold(), "Building cargo project".bright_green().bold() ); build_cargo_project(&crate_metadata, verbosity, unstable_options)?; println!( " {} {}", - "[3/4]".bold(), + "[2/3]".bold(), "Post processing wasm file".bright_green().bold() ); post_process_wasm(&crate_metadata)?; println!( " {} {}", - "[4/4]".bold(), + "[3/3]".bold(), "Optimizing wasm file".bright_green().bold() ); optimize_wasm(&crate_metadata)?; - - Ok(crate_metadata.dest_wasm) + Ok(()) } #[cfg(feature = "test-ci-only")] diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index 56fcfaa77..f7c7cb31a 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -20,7 +20,7 @@ use anyhow::{Context, Result}; use sp_core::H256; use subxt::{contracts::*, ClientBuilder, DefaultNodeRuntime}; -use crate::{cmd::build, ExtrinsicOpts}; +use crate::{cmd::build, ExtrinsicOpts, crate_metadata}; /// Load the wasm blob from the specified path. /// @@ -28,7 +28,10 @@ use crate::{cmd::build, ExtrinsicOpts}; fn load_contract_code(path: Option<&PathBuf>) -> Result> { let contract_wasm_path = match path { Some(path) => path.clone(), - None => build::collect_crate_metadata(&Default::default())?.dest_wasm, + None => { + let metadata = crate_metadata::CrateMetadata::collect(&Default::default())?; + metadata.dest_wasm + }, }; log::info!("Contract code path: {}", contract_wasm_path.display()); let mut data = Vec::new(); diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 4a26550b5..b0a6c39cc 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -18,6 +18,8 @@ mod contract; use cargo_metadata::Metadata; use crate::{ + crate_metadata::CrateMetadata, + cmd::build, util, workspace::{ManifestPath, Workspace}, UnstableFlags, Verbosity, @@ -28,13 +30,16 @@ use contract::{ SourceLanguage, User, }; use semver::Version; -use std::fs; +use std::{ + fs, + path::Path, +}; const METADATA_FILE: &str = "metadata.json"; /// Executes the metadata generation process struct GenerateMetadataCommand { - manifest_path: ManifestPath, + crate_metadata: CrateMetadata, verbosity: Option, unstable_options: UnstableFlags, } @@ -44,7 +49,8 @@ impl GenerateMetadataCommand { util::assert_channel()?; println!(" Generating metadata"); - let (cargo_meta, root_package_id) = crate::util::get_cargo_metadata(&self.manifest_path)?; + super::execute_build(&self.crate_metadata, self.verbosity, self.unstable_options.clone())?; + let cargo_meta = &self.crate_metadata.cargo_meta; let out_path = cargo_meta.target_directory.join(METADATA_FILE); let out_path_display = format!("{}", out_path.display()); @@ -52,7 +58,7 @@ impl GenerateMetadataCommand { let target_dir = cargo_meta.target_directory.clone(); // build the extended contract project metadata - let (source_meta, contract_meta, user_meta) = self.extended_metadata(&cargo_meta)?; + let (source_meta, contract_meta, user_meta) = self.extended_metadata()?; let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { let target_dir_arg = format!("--target-dir={}", target_dir.to_string_lossy()); @@ -66,7 +72,7 @@ impl GenerateMetadataCommand { "--release", // "--no-default-features", // Breaks builds for MacOS (linker errors), we should investigate this issue asap! ], - self.manifest_path.directory(), + self.crate_metadata.manifest_path.directory(), self.verbosity, )?; @@ -79,9 +85,9 @@ impl GenerateMetadataCommand { }; if self.unstable_options.original_manifest { - generate_metadata(&self.manifest_path)?; + generate_metadata(&self.crate_metadata.manifest_path)?; } else { - Workspace::new(&cargo_meta, &root_package_id)? + Workspace::new(&cargo_meta, &self.crate_metadata.root_package.id)? .with_root_package_manifest(|manifest| { manifest .with_added_crate_type("rlib")? @@ -99,11 +105,11 @@ impl GenerateMetadataCommand { } /// Generate the extended contract project metadata - fn extended_metadata(&self, cargo_meta: &Metadata) -> Result<(Source, Contract, Option)> { + fn extended_metadata(&self) -> Result<(Source, Contract, Option)> { // todo: generate these params let ink_version = Version::new(2, 1, 0); let rust_version = Version::new(1, 41, 0); - let contract_name = "test".to_string(); + let contract_name = self.crate_metadata.package_name.clone(); let contract_version = Version::new(0, 0, 0); let contract_authors = vec!["author@example.com".to_string()]; // optional @@ -140,8 +146,7 @@ impl GenerateMetadataCommand { /// Compile the contract and then hash the resulting wasm fn wasm_hash(&self) -> Result<[u8; 32]> { - let wasm_path = super::execute_build(self.manifest_path.clone(), self.verbosity, self.unstable_options.clone())?; - let wasm = fs::read(wasm_path)?; + let wasm = fs::read(&self.crate_metadata.dest_wasm)?; use ::blake2::digest::{ Update as _, @@ -163,8 +168,9 @@ pub(crate) fn execute_generate_metadata( verbosity: Option, unstable_options: UnstableFlags, ) -> Result { + let crate_metadata = CrateMetadata::collect(&manifest_path)?; GenerateMetadataCommand { - manifest_path, + crate_metadata, verbosity, unstable_options, }.exec() diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs new file mode 100644 index 000000000..9cce91011 --- /dev/null +++ b/src/crate_metadata.rs @@ -0,0 +1,89 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// ink! 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. +// +// ink! 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 ink!. If not, see . + +use crate::{workspace::ManifestPath, Verbosity}; +use anyhow::{Context, Result}; +use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, PackageId, Package}; +use rustc_version::Channel; +use std::{ffi::OsStr, path::{Path, PathBuf}, process::Command}; + +/// Relevant metadata obtained from Cargo.toml. +#[derive(Debug)] +pub struct CrateMetadata { + pub manifest_path: ManifestPath, + pub cargo_meta: cargo_metadata::Metadata, + pub package_name: String, + pub root_package: Package, + pub original_wasm: PathBuf, + pub dest_wasm: PathBuf, +} + +impl CrateMetadata { + /// Parses the contract manifest and returns relevant metadata. + pub fn collect(manifest_path: &ManifestPath) -> Result { + let (metadata, root_package_id) = get_cargo_metadata(manifest_path)?; + + // Find the root package by id in the list of packages. It is logical error if the root + // package is not found in the list. + let root_package = metadata + .packages + .iter() + .find(|package| package.id == root_package_id) + .expect("The package is not found in the `cargo metadata` output") + .clone(); + + // Normalize the package name. + let package_name = root_package.name.replace("-", "_"); + + // {target_dir}/wasm32-unknown-unknown/release/{package_name}.wasm + let mut original_wasm = metadata.target_directory.clone(); + original_wasm.push("wasm32-unknown-unknown"); + original_wasm.push("release"); + original_wasm.push(package_name.clone()); + original_wasm.set_extension("wasm"); + + // {target_dir}/{package_name}.wasm + let mut dest_wasm = metadata.target_directory.clone(); + dest_wasm.push(package_name.clone()); + dest_wasm.set_extension("wasm"); + + let crate_metadata = CrateMetadata { + manifest_path: manifest_path.clone(), + cargo_meta: metadata, + root_package: root_package.clone(), + package_name, + original_wasm, + dest_wasm, + }; + Ok(crate_metadata) + } +} + +/// Get the result of `cargo metadata`, together with the root package id. +fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, PackageId)> { + let mut cmd = MetadataCommand::new(); + let metadata = cmd + .manifest_path(manifest_path) + .exec() + .context("Error invoking `cargo metadata`")?; + let root_package_id = metadata + .resolve + .as_ref() + .and_then(|resolve| resolve.root.as_ref()) + .context("Cannot infer the root project id")? + .clone(); + Ok((metadata, root_package_id)) +} diff --git a/src/main.rs b/src/main.rs index 9f14c4bc4..d20b0f74f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ // along with ink!. If not, see . mod cmd; +mod crate_metadata; mod util; mod workspace; @@ -241,14 +242,15 @@ fn exec(cmd: Command) -> Result { verbosity, unstable_options, } => { - let dest_wasm = cmd::execute_build( - Default::default(), + let crate_metadata = crate_metadata::CrateMetadata::collect(&Default::default())?; + cmd::execute_build( + &crate_metadata, verbosity.try_into()?, unstable_options.try_into()?, )?; Ok(format!( "\nYour contract is ready. You can find it here:\n{}", - dest_wasm.display().to_string().bold() + crate_metadata.dest_wasm.display().to_string().bold() )) }, Command::GenerateMetadata { diff --git a/src/util.rs b/src/util.rs index 1260393c5..a632f0930 100644 --- a/src/util.rs +++ b/src/util.rs @@ -20,22 +20,6 @@ use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, PackageId}; use rustc_version::Channel; use std::{ffi::OsStr, path::Path, process::Command}; -/// Get the result of `cargo metadata`, together with the root package id. -pub fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, PackageId)> { - let mut cmd = MetadataCommand::new(); - let metadata = cmd - .manifest_path(manifest_path) - .exec() - .context("Error invoking `cargo metadata`")?; - let root_package_id = metadata - .resolve - .as_ref() - .and_then(|resolve| resolve.root.as_ref()) - .context("Cannot infer the root project id")? - .clone(); - Ok((metadata, root_package_id)) -} - /// Check whether the current rust channel is valid: `nightly` is recommended. pub fn assert_channel() -> Result<()> { let meta = rustc_version::version_meta()?; From 733977497768fc8e82165dbff79b8ba5a68a13e2 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 13:50:31 +0100 Subject: [PATCH 18/52] Fmt --- src/cmd/deploy.rs | 4 +- src/cmd/metadata/contract.rs | 6 +- src/cmd/metadata/mod.rs | 31 +++++----- src/crate_metadata.rs | 116 ++++++++++++++++++----------------- src/main.rs | 2 +- 5 files changed, 81 insertions(+), 78 deletions(-) diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index 9e8e36063..24a43f494 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -20,7 +20,7 @@ use anyhow::{Context, Result}; use sp_core::H256; use subxt::{contracts::*, ClientBuilder, DefaultNodeRuntime}; -use crate::{cmd::build, ExtrinsicOpts, crate_metadata}; +use crate::{cmd::build, crate_metadata, ExtrinsicOpts}; /// Load the wasm blob from the specified path. /// @@ -31,7 +31,7 @@ fn load_contract_code(path: Option<&PathBuf>) -> Result> { None => { let metadata = crate_metadata::CrateMetadata::collect(&Default::default())?; metadata.dest_wasm - }, + } }; log::info!("Contract code path: {}", contract_wasm_path.display()); let mut data = Vec::new(); diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 8243ab3dd..d9c732121 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -1,18 +1,18 @@ // Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // -// ink! is free software: you can redistribute it and/or modify +// 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. // -// ink! is distributed in the hope that it will be useful, +// 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 ink!. If not, see . +// along with cargo-contract. If not, see . use core::fmt::{Display, Formatter, Result as DisplayResult, Write}; use semver::Version; diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index b0a6c39cc..220a5b674 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -1,39 +1,36 @@ // Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // -// ink! is free software: you can redistribute it and/or modify +// 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. // -// ink! is distributed in the hope that it will be useful, +// 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 ink!. If not, see . +// along with cargo-contract. If not, see . mod contract; -use cargo_metadata::Metadata; use crate::{ - crate_metadata::CrateMetadata, cmd::build, + crate_metadata::CrateMetadata, util, workspace::{ManifestPath, Workspace}, UnstableFlags, Verbosity, }; use anyhow::Result; +use cargo_metadata::Metadata; use contract::{ Compiler, Contract, ContractMetadata, Language, License, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; -use std::{ - fs, - path::Path, -}; +use std::{fs, path::Path}; const METADATA_FILE: &str = "metadata.json"; @@ -45,11 +42,15 @@ struct GenerateMetadataCommand { } impl GenerateMetadataCommand { - pub fn exec(&self)-> Result { + pub fn exec(&self) -> Result { util::assert_channel()?; println!(" Generating metadata"); - super::execute_build(&self.crate_metadata, self.verbosity, self.unstable_options.clone())?; + super::execute_build( + &self.crate_metadata, + self.verbosity, + self.unstable_options.clone(), + )?; let cargo_meta = &self.crate_metadata.cargo_meta; let out_path = cargo_meta.target_directory.join(METADATA_FILE); @@ -148,10 +149,7 @@ impl GenerateMetadataCommand { fn wasm_hash(&self) -> Result<[u8; 32]> { let wasm = fs::read(&self.crate_metadata.dest_wasm)?; - use ::blake2::digest::{ - Update as _, - VariableOutput as _, - }; + use ::blake2::digest::{Update as _, VariableOutput as _}; let mut output = [0u8; 32]; let mut blake2 = blake2::VarBlake2b::new_keyed(&[], 32); blake2.update(wasm); @@ -173,7 +171,8 @@ pub(crate) fn execute_generate_metadata( crate_metadata, verbosity, unstable_options, - }.exec() + } + .exec() } #[cfg(feature = "test-ci-only")] diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index 9cce91011..fe81e747d 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -1,89 +1,93 @@ // Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // -// ink! is free software: you can redistribute it and/or modify +// 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. // -// ink! is distributed in the hope that it will be useful, +// 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 ink!. If not, see . +// along with cargo-contract. If not, see . use crate::{workspace::ManifestPath, Verbosity}; use anyhow::{Context, Result}; -use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, PackageId, Package}; +use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; use rustc_version::Channel; -use std::{ffi::OsStr, path::{Path, PathBuf}, process::Command}; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, + process::Command, +}; /// Relevant metadata obtained from Cargo.toml. #[derive(Debug)] pub struct CrateMetadata { - pub manifest_path: ManifestPath, - pub cargo_meta: cargo_metadata::Metadata, - pub package_name: String, - pub root_package: Package, - pub original_wasm: PathBuf, - pub dest_wasm: PathBuf, + pub manifest_path: ManifestPath, + pub cargo_meta: cargo_metadata::Metadata, + pub package_name: String, + pub root_package: Package, + pub original_wasm: PathBuf, + pub dest_wasm: PathBuf, } impl CrateMetadata { - /// Parses the contract manifest and returns relevant metadata. - pub fn collect(manifest_path: &ManifestPath) -> Result { - let (metadata, root_package_id) = get_cargo_metadata(manifest_path)?; + /// Parses the contract manifest and returns relevant metadata. + pub fn collect(manifest_path: &ManifestPath) -> Result { + let (metadata, root_package_id) = get_cargo_metadata(manifest_path)?; - // Find the root package by id in the list of packages. It is logical error if the root - // package is not found in the list. - let root_package = metadata - .packages - .iter() - .find(|package| package.id == root_package_id) - .expect("The package is not found in the `cargo metadata` output") - .clone(); + // Find the root package by id in the list of packages. It is logical error if the root + // package is not found in the list. + let root_package = metadata + .packages + .iter() + .find(|package| package.id == root_package_id) + .expect("The package is not found in the `cargo metadata` output") + .clone(); - // Normalize the package name. - let package_name = root_package.name.replace("-", "_"); + // Normalize the package name. + let package_name = root_package.name.replace("-", "_"); - // {target_dir}/wasm32-unknown-unknown/release/{package_name}.wasm - let mut original_wasm = metadata.target_directory.clone(); - original_wasm.push("wasm32-unknown-unknown"); - original_wasm.push("release"); - original_wasm.push(package_name.clone()); - original_wasm.set_extension("wasm"); + // {target_dir}/wasm32-unknown-unknown/release/{package_name}.wasm + let mut original_wasm = metadata.target_directory.clone(); + original_wasm.push("wasm32-unknown-unknown"); + original_wasm.push("release"); + original_wasm.push(package_name.clone()); + original_wasm.set_extension("wasm"); - // {target_dir}/{package_name}.wasm - let mut dest_wasm = metadata.target_directory.clone(); - dest_wasm.push(package_name.clone()); - dest_wasm.set_extension("wasm"); + // {target_dir}/{package_name}.wasm + let mut dest_wasm = metadata.target_directory.clone(); + dest_wasm.push(package_name.clone()); + dest_wasm.set_extension("wasm"); - let crate_metadata = CrateMetadata { - manifest_path: manifest_path.clone(), - cargo_meta: metadata, - root_package: root_package.clone(), - package_name, - original_wasm, - dest_wasm, - }; - Ok(crate_metadata) - } + let crate_metadata = CrateMetadata { + manifest_path: manifest_path.clone(), + cargo_meta: metadata, + root_package: root_package.clone(), + package_name, + original_wasm, + dest_wasm, + }; + Ok(crate_metadata) + } } /// Get the result of `cargo metadata`, together with the root package id. fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, PackageId)> { - let mut cmd = MetadataCommand::new(); - let metadata = cmd - .manifest_path(manifest_path) - .exec() - .context("Error invoking `cargo metadata`")?; - let root_package_id = metadata - .resolve - .as_ref() - .and_then(|resolve| resolve.root.as_ref()) - .context("Cannot infer the root project id")? - .clone(); - Ok((metadata, root_package_id)) + let mut cmd = MetadataCommand::new(); + let metadata = cmd + .manifest_path(manifest_path) + .exec() + .context("Error invoking `cargo metadata`")?; + let root_package_id = metadata + .resolve + .as_ref() + .and_then(|resolve| resolve.root.as_ref()) + .context("Cannot infer the root project id")? + .clone(); + Ok((metadata, root_package_id)) } diff --git a/src/main.rs b/src/main.rs index c1e096f35..58eb10c3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -252,7 +252,7 @@ fn exec(cmd: Command) -> Result { "\nYour contract is ready. You can find it here:\n{}", crate_metadata.dest_wasm.display().to_string().bold() )) - }, + } Command::GenerateMetadata { verbosity, unstable_options, From 8d11f56af1b166aa49abfb07667e9d58d6fd2a09 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 13:57:58 +0100 Subject: [PATCH 19/52] Rust version --- src/cmd/metadata/mod.rs | 2 +- src/crate_metadata.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 220a5b674..58f60c0d6 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -109,7 +109,7 @@ impl GenerateMetadataCommand { fn extended_metadata(&self) -> Result<(Source, Contract, Option)> { // todo: generate these params let ink_version = Version::new(2, 1, 0); - let rust_version = Version::new(1, 41, 0); + let rust_version = Version::parse(&rustc_version::version()?.to_string())?; let contract_name = self.crate_metadata.package_name.clone(); let contract_version = Version::new(0, 0, 0); let contract_authors = vec!["author@example.com".to_string()]; diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index fe81e747d..73a34c394 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -17,7 +17,6 @@ use crate::{workspace::ManifestPath, Verbosity}; use anyhow::{Context, Result}; use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; -use rustc_version::Channel; use std::{ ffi::OsStr, path::{Path, PathBuf}, From aafbb4183658f1e1333512e8d042804a5656c9e0 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 14:45:04 +0100 Subject: [PATCH 20/52] Contract version --- src/cmd/metadata/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 58f60c0d6..8595fda73 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -111,7 +111,8 @@ impl GenerateMetadataCommand { let ink_version = Version::new(2, 1, 0); let rust_version = Version::parse(&rustc_version::version()?.to_string())?; let contract_name = self.crate_metadata.package_name.clone(); - let contract_version = Version::new(0, 0, 0); + let contract_version = + Version::parse(&self.crate_metadata.root_package.version.to_string())?; let contract_authors = vec!["author@example.com".to_string()]; // optional let description: Option = None; From 0345b7cd9daf6a15e330368fcf7d9a0bd142b66d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 9 Jul 2020 15:24:08 +0100 Subject: [PATCH 21/52] Add remaining optional enhanced metadata --- src/cmd/build.rs | 2 -- src/cmd/deploy.rs | 2 +- src/cmd/metadata/mod.rs | 23 +++++++++++++---------- src/crate_metadata.rs | 17 +++++++++++------ src/util.rs | 3 +-- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index ddb7c31b1..b99f1af31 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -17,7 +17,6 @@ use std::{ fs::metadata, io::{self, Write}, - path::{Path, PathBuf}, process::Command, }; @@ -28,7 +27,6 @@ use crate::{ UnstableFlags, Verbosity, }; use anyhow::{Context, Result}; -use cargo_metadata::Package; use colored::Colorize; use parity_wasm::elements::{External, MemoryType, Module, Section}; diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index 24a43f494..c269f1beb 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -20,7 +20,7 @@ use anyhow::{Context, Result}; use sp_core::H256; use subxt::{contracts::*, ClientBuilder, DefaultNodeRuntime}; -use crate::{cmd::build, crate_metadata, ExtrinsicOpts}; +use crate::{crate_metadata, ExtrinsicOpts}; /// Load the wasm blob from the specified path. /// diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 8595fda73..f82c025d2 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -17,20 +17,18 @@ mod contract; use crate::{ - cmd::build, crate_metadata::CrateMetadata, util, workspace::{ManifestPath, Workspace}, UnstableFlags, Verbosity, }; use anyhow::Result; -use cargo_metadata::Metadata; use contract::{ Compiler, Contract, ContractMetadata, Language, License, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; -use std::{fs, path::Path}; +use std::fs; const METADATA_FILE: &str = "metadata.json"; @@ -108,18 +106,23 @@ impl GenerateMetadataCommand { /// Generate the extended contract project metadata fn extended_metadata(&self) -> Result<(Source, Contract, Option)> { // todo: generate these params + let contract_package = &self.crate_metadata.root_package; let ink_version = Version::new(2, 1, 0); let rust_version = Version::parse(&rustc_version::version()?.to_string())?; - let contract_name = self.crate_metadata.package_name.clone(); + let contract_name = contract_package.name.clone(); let contract_version = - Version::parse(&self.crate_metadata.root_package.version.to_string())?; + Version::parse(&contract_package.version.to_string())?; let contract_authors = vec!["author@example.com".to_string()]; // optional - let description: Option = None; - let documentation = None; - let repository = None; - let homepage = None; - let license: Option = None; + let description = contract_package.description.clone(); + let documentation = self.crate_metadata.documentation.clone(); + let repository = contract_package.repository.clone(); + let homepage = self.crate_metadata.homepage.clone(); + let license = contract_package.license + .map(|license| Some(License::SpdxId(license))) + .unwrap_or_else(|| { + contract_package.license_file.map(|f| License::Link(url::Url::from_file_path(f)?)) + }); let hash = self.wasm_hash()?; diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index 73a34c394..f2efb3d4e 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::{workspace::ManifestPath, Verbosity}; +use crate::workspace::ManifestPath; use anyhow::{Context, Result}; use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, - process::Command, -}; +use std::path::PathBuf; +use url::Url; /// Relevant metadata obtained from Cargo.toml. #[derive(Debug)] @@ -32,6 +29,8 @@ pub struct CrateMetadata { pub root_package: Package, pub original_wasm: PathBuf, pub dest_wasm: PathBuf, + pub documentation: Option, + pub homepage: Option, } impl CrateMetadata { @@ -63,6 +62,10 @@ impl CrateMetadata { dest_wasm.push(package_name.clone()); dest_wasm.set_extension("wasm"); + // todo: read directly from Cargo.toml + let documentation = None; + let homepage = None; + let crate_metadata = CrateMetadata { manifest_path: manifest_path.clone(), cargo_meta: metadata, @@ -70,6 +73,8 @@ impl CrateMetadata { package_name, original_wasm, dest_wasm, + documentation, + homepage, }; Ok(crate_metadata) } diff --git a/src/util.rs b/src/util.rs index 8f06d4001..53ec822b0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -use crate::{workspace::ManifestPath, Verbosity}; +use crate::Verbosity; use anyhow::{Context, Result}; -use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, PackageId}; use rustc_version::Channel; use std::{ffi::OsStr, path::Path, process::Command}; From 767e1eb695df6ad21ea769e83c28db7e80fd2eb8 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 09:10:16 +0100 Subject: [PATCH 22/52] Move wasm build, make repo and license work --- src/cmd/metadata/mod.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index f82c025d2..2f05c9b4e 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -29,6 +29,7 @@ use contract::{ }; use semver::Version; use std::fs; +use url::Url; const METADATA_FILE: &str = "metadata.json"; @@ -44,11 +45,6 @@ impl GenerateMetadataCommand { util::assert_channel()?; println!(" Generating metadata"); - super::execute_build( - &self.crate_metadata, - self.verbosity, - self.unstable_options.clone(), - )?; let cargo_meta = &self.crate_metadata.cargo_meta; let out_path = cargo_meta.target_directory.join(METADATA_FILE); @@ -112,18 +108,21 @@ impl GenerateMetadataCommand { let contract_name = contract_package.name.clone(); let contract_version = Version::parse(&contract_package.version.to_string())?; - let contract_authors = vec!["author@example.com".to_string()]; + let contract_authors = contract_package.authors.clone(); // optional let description = contract_package.description.clone(); let documentation = self.crate_metadata.documentation.clone(); - let repository = contract_package.repository.clone(); + let repository = contract_package.repository + .as_ref() + .map(|repo| Url::parse(&repo)) + .transpose()?; let homepage = self.crate_metadata.homepage.clone(); let license = contract_package.license - .map(|license| Some(License::SpdxId(license))) - .unwrap_or_else(|| { - contract_package.license_file.map(|f| License::Link(url::Url::from_file_path(f)?)) - }); - + .as_ref() + .map(|license| License::SpdxId(license.clone())); + // .map_or_else(|| { + // contract_package.license_file.map(|f| url::Url::from_file_path(f).unwrap()) + // }, Ok)? let hash = self.wasm_hash()?; let source = { @@ -151,6 +150,12 @@ impl GenerateMetadataCommand { /// Compile the contract and then hash the resulting wasm fn wasm_hash(&self) -> Result<[u8; 32]> { + super::execute_build( + &self.crate_metadata, + self.verbosity, + self.unstable_options.clone(), + )?; + let wasm = fs::read(&self.crate_metadata.dest_wasm)?; use ::blake2::digest::{Update as _, VariableOutput as _}; From be4c7b122fb3fbaafdd3a52f663613cb8036a782 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 10:30:22 +0100 Subject: [PATCH 23/52] Just use a String for the license --- src/cmd/metadata/contract.rs | 13 ++----------- src/cmd/metadata/mod.rs | 9 ++------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index d9c732121..6f1c4f344 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -173,7 +173,7 @@ pub struct Contract { #[serde(skip_serializing_if = "Option::is_none")] homepage: Option, #[serde(skip_serializing_if = "Option::is_none")] - license: Option, + license: Option, } impl Contract { @@ -186,7 +186,7 @@ impl Contract { documentation: Option, repository: Option, homepage: Option, - license: Option, + license: Option, ) -> Self { Contract { name, @@ -201,15 +201,6 @@ impl Contract { } } -/// The license of a smart contract -#[derive(Debug, Serialize)] -pub enum License { - /// An [SPDX identifier](https://spdx.org/licenses/) - SpdxId(String), - /// A URL to a custom license - Link(Url), -} - /// Additional user defined metadata, can be any valid json. #[derive(Debug, Serialize)] pub struct User { diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 2f05c9b4e..c996b10d9 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -24,7 +24,7 @@ use crate::{ }; use anyhow::Result; use contract::{ - Compiler, Contract, ContractMetadata, Language, License, Source, SourceCompiler, + Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; @@ -117,12 +117,7 @@ impl GenerateMetadataCommand { .map(|repo| Url::parse(&repo)) .transpose()?; let homepage = self.crate_metadata.homepage.clone(); - let license = contract_package.license - .as_ref() - .map(|license| License::SpdxId(license.clone())); - // .map_or_else(|| { - // contract_package.license_file.map(|f| url::Url::from_file_path(f).unwrap()) - // }, Ok)? + let license = contract_package.license.clone(); let hash = self.wasm_hash()?; let source = { From 1f4eaa1f5c8fc40521e8d267813284fde98a28c0 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 10:31:50 +0100 Subject: [PATCH 24/52] Remove unused variants --- src/cmd/metadata/contract.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 6f1c4f344..d6571f184 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -104,7 +104,6 @@ pub enum Language { Ink, Solidity, AssemblyScript, - Other(&'static str), } impl Display for Language { @@ -113,7 +112,6 @@ impl Display for Language { Self::Ink => write!(f, "ink!"), Self::Solidity => write!(f, "Solidity"), Self::AssemblyScript => write!(f, "AssemblyScript"), - Self::Other(lang) => write!(f, "{}", lang), } } } @@ -145,7 +143,6 @@ impl SourceCompiler { pub enum Compiler { RustC, Solang, - Other(&'static str), } impl Display for Compiler { @@ -153,7 +150,6 @@ impl Display for Compiler { match self { Self::RustC => write!(f, "rustc"), Self::Solang => write!(f, "solang"), - Self::Other(other) => write!(f, "{}", other), } } } @@ -213,10 +209,6 @@ impl User { pub fn new(json: Map) -> Self { User { json } } - - pub fn from_str(json: &str) -> serde_json::Result { - serde_json::from_str(json.as_ref()).map(Self::new) - } } /// Serializes the given bytes as byte string. From da0055a402cfc4180453b7ef7a2197d8cd21ed1f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 11:28:13 +0100 Subject: [PATCH 25/52] Read docs and homepage urls directly from manifest --- src/cmd/metadata/mod.rs | 3 ++- src/crate_metadata.rs | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index c996b10d9..25ae72803 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -138,7 +138,8 @@ impl GenerateMetadataCommand { license, ); - let user: Option = None; + // user defined metadata + let user = self.crate_metadata.user.clone().map(User::new); Ok((source, contract, user)) } diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index f2efb3d4e..4f494dd6f 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -17,7 +17,12 @@ use crate::workspace::ManifestPath; use anyhow::{Context, Result}; use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; -use std::path::PathBuf; +use serde_json::{Map, Value}; +use std::{ + fs, + path::PathBuf, +}; +use toml::value; use url::Url; /// Relevant metadata obtained from Cargo.toml. @@ -31,6 +36,7 @@ pub struct CrateMetadata { pub dest_wasm: PathBuf, pub documentation: Option, pub homepage: Option, + pub user: Option>, } impl CrateMetadata { @@ -62,9 +68,7 @@ impl CrateMetadata { dest_wasm.push(package_name.clone()); dest_wasm.set_extension("wasm"); - // todo: read directly from Cargo.toml - let documentation = None; - let homepage = None; + let (documentation, homepage, user) = get_cargo_toml_metadata(manifest_path)?; let crate_metadata = CrateMetadata { manifest_path: manifest_path.clone(), @@ -75,6 +79,7 @@ impl CrateMetadata { dest_wasm, documentation, homepage, + user, }; Ok(crate_metadata) } @@ -95,3 +100,28 @@ fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Pa .clone(); Ok((metadata, root_package_id)) } + +/// Read extra metadata not available via `cargo metadata` directly from `Cargo.toml` +fn get_cargo_toml_metadata( + manifest_path: &ManifestPath +) -> Result<(Option, Option, Option>)> { + let toml = fs::read_to_string(manifest_path)?; + let toml: value::Table = toml::from_str(&toml)?; + + let get_url = |field_name| -> Result> { + toml + .get("package") + .ok_or(anyhow::anyhow!("package section not found"))? + .get(field_name) + .and_then(|v| v.as_str()) + .map(Url::parse) + .transpose() + .context(format!("{} should be a valid URL", field_name)) + .map_err(Into::into) + }; + + let documentation = get_url("documentation")?; + let homepage = get_url("homepage")?; + + Ok((documentation, homepage, None)) +} From 0e0c17dd7a0c7bce98f9ab17e361f93f0430e15e Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 11:51:52 +0100 Subject: [PATCH 26/52] Read user defined metadata --- src/crate_metadata.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index 4f494dd6f..1531f8d1c 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -123,5 +123,18 @@ fn get_cargo_toml_metadata( let documentation = get_url("documentation")?; let homepage = get_url("homepage")?; - Ok((documentation, homepage, None)) + let user = toml + .get("package") + .and_then(|v| v.get("metadata")) + .and_then(|v| v.get("contract")) + .and_then(|v| v.get("user")) + .and_then(|v| v.as_table()) + .map(|v| { + // convert user defined section from toml to json + serde_json::to_string(v) + .and_then(|json| serde_json::from_str(&json)) + }) + .transpose()?; + + Ok((documentation, homepage, user)) } From c1ec746a0ea921386c79f4c7b22ecde187a9f45d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 16:06:34 +0100 Subject: [PATCH 27/52] Fmt --- src/cmd/metadata/mod.rs | 9 ++++----- src/crate_metadata.rs | 13 ++++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 25ae72803..5f26e213a 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -24,8 +24,7 @@ use crate::{ }; use anyhow::Result; use contract::{ - Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler, - SourceLanguage, User, + Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; use std::fs; @@ -106,13 +105,13 @@ impl GenerateMetadataCommand { let ink_version = Version::new(2, 1, 0); let rust_version = Version::parse(&rustc_version::version()?.to_string())?; let contract_name = contract_package.name.clone(); - let contract_version = - Version::parse(&contract_package.version.to_string())?; + let contract_version = Version::parse(&contract_package.version.to_string())?; let contract_authors = contract_package.authors.clone(); // optional let description = contract_package.description.clone(); let documentation = self.crate_metadata.documentation.clone(); - let repository = contract_package.repository + let repository = contract_package + .repository .as_ref() .map(|repo| Url::parse(&repo)) .transpose()?; diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index 1531f8d1c..2e69d8c9c 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -18,10 +18,7 @@ use crate::workspace::ManifestPath; use anyhow::{Context, Result}; use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; use serde_json::{Map, Value}; -use std::{ - fs, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; use toml::value; use url::Url; @@ -103,14 +100,13 @@ fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Pa /// Read extra metadata not available via `cargo metadata` directly from `Cargo.toml` fn get_cargo_toml_metadata( - manifest_path: &ManifestPath + manifest_path: &ManifestPath, ) -> Result<(Option, Option, Option>)> { let toml = fs::read_to_string(manifest_path)?; let toml: value::Table = toml::from_str(&toml)?; let get_url = |field_name| -> Result> { - toml - .get("package") + toml.get("package") .ok_or(anyhow::anyhow!("package section not found"))? .get(field_name) .and_then(|v| v.as_str()) @@ -131,8 +127,7 @@ fn get_cargo_toml_metadata( .and_then(|v| v.as_table()) .map(|v| { // convert user defined section from toml to json - serde_json::to_string(v) - .and_then(|json| serde_json::from_str(&json)) + serde_json::to_string(v).and_then(|json| serde_json::from_str(&json)) }) .transpose()?; From 9bc614cd62e67113fecb927c8f1c355690db6df5 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 16:27:20 +0100 Subject: [PATCH 28/52] This PR no longer depends on an updated version of ink! --- templates/new/_Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index 68c26a6ee..df71d79d9 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -5,10 +5,10 @@ authors = ["[your_name] <[your_email]>"] edition = "2018" [dependencies] -ink_metadata = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } -ink_primitives = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", default-features = false } -ink_core = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_core", default-features = false } -ink_lang = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_lang", default-features = false } +ink_metadata = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } +ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } +ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } +ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } From 3aa9d3f3f4907c9089040ebbccbf3728f6cfa351 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 17:43:39 +0100 Subject: [PATCH 29/52] Fix test error, refactor commands to use common method name --- src/cmd/build.rs | 36 +++++++++++++++++++++++++++++------- src/cmd/metadata/mod.rs | 10 +++++----- src/cmd/mod.rs | 9 +++------ src/cmd/new.rs | 12 ++++++------ src/main.rs | 12 ++++++------ 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index b99f1af31..da86b411d 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -17,6 +17,7 @@ use std::{ fs::metadata, io::{self, Write}, + path::PathBuf, process::Command, }; @@ -239,12 +240,33 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> { /// Executes build of the smart-contract which produces a wasm binary that is ready for deploying. /// -/// It does so by invoking build by cargo and then post processing the final binary. -pub(crate) fn execute_build( +/// It does so by invoking `cargo build` and then post processing the final binary. +/// +/// # Note +/// +/// Collects the contract crate's metadata using the supplied manifest (`Cargo.toml`) path. Use +/// [`execute_build_with_metadata`] if an instance is already available. +pub(crate) fn execute( + manifest_path: &ManifestPath, + verbosity: Option, + unstable_options: UnstableFlags, +) -> Result { + let crate_metadata = CrateMetadata::collect(manifest_path)?; + execute_with_metadata(&crate_metadata, verbosity, unstable_options) +} + +/// Executes build of the smart-contract which produces a wasm binary that is ready for deploying. +/// +/// It does so by invoking `cargo build` and then post processing the final binary. +/// +/// # Note +/// +/// Uses the supplied `CrateMetadata`. If an instance is not available use [`execute_build`] +pub(crate) fn execute_with_metadata( crate_metadata: &CrateMetadata, verbosity: Option, unstable_options: UnstableFlags, -) -> Result<()> { +) -> Result { println!( " {} {}", "[1/3]".bold(), @@ -263,23 +285,23 @@ pub(crate) fn execute_build( "Optimizing wasm file".bright_green().bold() ); optimize_wasm(&crate_metadata)?; - Ok(()) + Ok(crate_metadata.dest_wasm.clone()) } #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { use crate::{ - cmd::execute_new, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags, + cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags, }; #[test] fn build_template() { with_tmp_dir(|path| { - execute_new("new_project", Some(path)).expect("new project creation failed"); + cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap(); - super::execute_build(manifest_path, None, UnstableFlags::default()) + super::execute(&manifest_path, None, UnstableFlags::default()) .expect("build failed"); }); } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 5f26e213a..616dc88c8 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -145,7 +145,7 @@ impl GenerateMetadataCommand { /// Compile the contract and then hash the resulting wasm fn wasm_hash(&self) -> Result<[u8; 32]> { - super::execute_build( + super::build::execute_with_metadata( &self.crate_metadata, self.verbosity, self.unstable_options.clone(), @@ -165,7 +165,7 @@ impl GenerateMetadataCommand { /// Generates a file with metadata describing the ABI of the smart-contract. /// /// It does so by generating and invoking a temporary workspace member. -pub(crate) fn execute_generate_metadata( +pub(crate) fn execute( manifest_path: ManifestPath, verbosity: Option, unstable_options: UnstableFlags, @@ -183,7 +183,7 @@ pub(crate) fn execute_generate_metadata( #[cfg(test)] mod tests { use crate::{ - cmd::{execute_generate_metadata, execute_new}, + cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags, @@ -193,10 +193,10 @@ mod tests { fn generate_metadata() { env_logger::try_init().ok(); with_tmp_dir(|path| { - execute_new("new_project", Some(path)).expect("new project creation failed"); + cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let working_dir = path.join("new_project"); let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml")).unwrap(); - let message = execute_generate_metadata(manifest_path, None, UnstableFlags::default()) + let message = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) .expect("generate metadata failed"); println!("{}", message); diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 9d9352010..6a7191d6d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -14,16 +14,13 @@ // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . -mod build; +pub mod build; #[cfg(feature = "extrinsics")] mod deploy; #[cfg(feature = "extrinsics")] mod instantiate; -mod metadata; -mod new; +pub mod metadata; +pub mod new; -pub(crate) use self::{ - build::execute_build, metadata::execute_generate_metadata, new::execute_new, -}; #[cfg(feature = "extrinsics")] pub(crate) use self::{deploy::execute_deploy, instantiate::execute_instantiate}; diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 90a8de542..7c35d103b 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -23,7 +23,7 @@ use std::{ use anyhow::Result; use heck::CamelCase as _; -pub(crate) fn execute_new(name: &str, dir: Option<&PathBuf>) -> Result { +pub(crate) fn execute(name: &str, dir: Option<&PathBuf>) -> Result { if name.contains('-') { anyhow::bail!("Contract names cannot contain hyphens"); } @@ -97,12 +97,12 @@ pub(crate) fn execute_new(name: &str, dir: Option<&PathBuf>) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::{cmd::execute_new, util::tests::with_tmp_dir}; + use crate::{cmd, util::tests::with_tmp_dir}; #[test] fn rejects_hyphenated_name() { with_tmp_dir(|path| { - let result = execute_new("rejects-hyphenated-name", Some(path)); + let result = cmd::new::execute("rejects-hyphenated-name", Some(path)); assert_eq!( format!("{:?}", result), r#"Err(Contract names cannot contain hyphens)"# @@ -114,8 +114,8 @@ mod tests { fn contract_cargo_project_already_exists() { with_tmp_dir(|path| { let name = "test_contract_cargo_project_already_exists"; - let _ = execute_new(name, Some(path)); - let result = execute_new(name, Some(path)); + let _ = execute(name, Some(path)); + let result = cmd::new::execute(name, Some(path)); assert!(result.is_err(), "Should fail"); assert_eq!( @@ -132,7 +132,7 @@ mod tests { let dir = path.join(name); fs::create_dir_all(&dir).unwrap(); fs::File::create(dir.join(".gitignore")).unwrap(); - let result = execute_new(name, Some(path)); + let result = cmd::new::execute(name, Some(path)); assert!(result.is_err(), "Should fail"); assert_eq!( diff --git a/src/main.rs b/src/main.rs index 58eb10c3c..fbbd6198e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -237,26 +237,26 @@ fn main() { fn exec(cmd: Command) -> Result { match &cmd { - Command::New { name, target_dir } => cmd::execute_new(name, target_dir.as_ref()), + Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()), Command::Build { verbosity, unstable_options, } => { - let crate_metadata = crate_metadata::CrateMetadata::collect(&Default::default())?; - cmd::execute_build( - &crate_metadata, + let manifest_path = Default::default(); + let dest_wasm = cmd::build::execute( + &manifest_path, verbosity.try_into()?, unstable_options.try_into()?, )?; Ok(format!( "\nYour contract is ready. You can find it here:\n{}", - crate_metadata.dest_wasm.display().to_string().bold() + dest_wasm.display().to_string().bold() )) } Command::GenerateMetadata { verbosity, unstable_options, - } => cmd::execute_generate_metadata( + } => cmd::metadata::execute( Default::default(), verbosity.try_into()?, unstable_options.try_into()?, From d9c6bfdf4af9f58536042ee1bca75b1a2263abec Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Fri, 10 Jul 2020 17:44:28 +0100 Subject: [PATCH 30/52] Fmt --- src/cmd/build.rs | 7 ++----- src/cmd/metadata/mod.rs | 7 +------ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index da86b411d..5c7553978 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -291,9 +291,7 @@ pub(crate) fn execute_with_metadata( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{ - cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags, - }; + use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; #[test] fn build_template() { @@ -301,8 +299,7 @@ mod tests { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap(); - super::execute(&manifest_path, None, UnstableFlags::default()) - .expect("build failed"); + super::execute(&manifest_path, None, UnstableFlags::default()).expect("build failed"); }); } } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 616dc88c8..841ba7db0 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -182,12 +182,7 @@ pub(crate) fn execute( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{ - cmd, - util::tests::with_tmp_dir, - workspace::ManifestPath, - UnstableFlags, - }; + use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; #[test] fn generate_metadata() { From 716272a249cc3a3889923f18792d17a9fbae5438 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 08:42:51 +0100 Subject: [PATCH 31/52] Refactor crate metadata collection --- src/crate_metadata.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index 2e69d8c9c..d077cb3c5 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -16,7 +16,7 @@ use crate::workspace::ManifestPath; use anyhow::{Context, Result}; -use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package, PackageId}; +use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package}; use serde_json::{Map, Value}; use std::{fs, path::PathBuf}; use toml::value; @@ -39,16 +39,7 @@ pub struct CrateMetadata { impl CrateMetadata { /// Parses the contract manifest and returns relevant metadata. pub fn collect(manifest_path: &ManifestPath) -> Result { - let (metadata, root_package_id) = get_cargo_metadata(manifest_path)?; - - // Find the root package by id in the list of packages. It is logical error if the root - // package is not found in the list. - let root_package = metadata - .packages - .iter() - .find(|package| package.id == root_package_id) - .expect("The package is not found in the `cargo metadata` output") - .clone(); + let (metadata, root_package) = get_cargo_metadata(manifest_path)?; // Normalize the package name. let package_name = root_package.name.replace("-", "_"); @@ -70,7 +61,7 @@ impl CrateMetadata { let crate_metadata = CrateMetadata { manifest_path: manifest_path.clone(), cargo_meta: metadata, - root_package: root_package.clone(), + root_package, package_name, original_wasm, dest_wasm, @@ -83,7 +74,7 @@ impl CrateMetadata { } /// Get the result of `cargo metadata`, together with the root package id. -fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, PackageId)> { +fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Package)> { let mut cmd = MetadataCommand::new(); let metadata = cmd .manifest_path(manifest_path) @@ -95,7 +86,15 @@ fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Pa .and_then(|resolve| resolve.root.as_ref()) .context("Cannot infer the root project id")? .clone(); - Ok((metadata, root_package_id)) + // Find the root package by id in the list of packages. It is logical error if the root + // package is not found in the list. + let root_package = metadata + .packages + .iter() + .find(|package| package.id == root_package_id) + .expect("The package is not found in the `cargo metadata` output") + .clone(); + Ok((metadata, root_package)) } /// Read extra metadata not available via `cargo metadata` directly from `Cargo.toml` From 70073f24f0a9a4a6e313973800cb8cff4dace13c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 10:05:07 +0100 Subject: [PATCH 32/52] Remove ink_lang dependency from generated metadata crate --- src/workspace/manifest.rs | 23 +++++++++-------------- src/workspace/metadata.rs | 2 -- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/workspace/manifest.rs b/src/workspace/manifest.rs index 7b8507542..6b505970d 100644 --- a/src/workspace/manifest.rs +++ b/src/workspace/manifest.rs @@ -361,20 +361,15 @@ impl Manifest { .as_str() .ok_or(anyhow::anyhow!("[lib] name should be a string"))?; - let get_dependency = |name| -> Result<&value::Table> { - self.toml - .get("dependencies") - .ok_or(anyhow::anyhow!("[dependencies] section not found"))? - .get(name) - .ok_or(anyhow::anyhow!("{} dependency not found", name))? - .as_table() - .ok_or(anyhow::anyhow!("{} dependency should be a table", name)) - }; - - let ink_lang = get_dependency("ink_lang")?; - let ink_metadata = get_dependency("ink_metadata")?; - - metadata::generate_package(dir, name, ink_lang.clone(), ink_metadata.clone())?; + let ink_metadata = self.toml + .get("dependencies") + .ok_or(anyhow::anyhow!("[dependencies] section not found"))? + .get("ink_metadata") + .ok_or(anyhow::anyhow!("{} dependency not found", name))? + .as_table() + .ok_or(anyhow::anyhow!("{} dependency should be a table", name))?; + + metadata::generate_package(dir, name, ink_metadata.clone())?; } let updated_toml = toml::to_string(&self.toml)?; diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs index 4ae3a1050..0f82bf3ad 100644 --- a/src/workspace/metadata.rs +++ b/src/workspace/metadata.rs @@ -28,7 +28,6 @@ use toml::value; pub(super) fn generate_package>( target_dir: P, contract_package_name: &str, - ink_lang_dependency: value::Table, mut ink_metadata_dependency: value::Table, ) -> Result<()> { let dir = target_dir.as_ref(); @@ -62,7 +61,6 @@ pub(super) fn generate_package>( ink_metadata_dependency.remove("optional"); // add ink dependencies copied from contract manifest - deps.insert("ink_lang".into(), ink_lang_dependency.into()); deps.insert("ink_metadata".into(), ink_metadata_dependency.into()); let cargo_toml = toml::to_string(&cargo_toml)?; From 3c9e42947f780369fb05141fc9ffb5ea2289d1ec Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 10:32:13 +0100 Subject: [PATCH 33/52] Fmt --- src/workspace/manifest.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/workspace/manifest.rs b/src/workspace/manifest.rs index 6b505970d..b96a8b67c 100644 --- a/src/workspace/manifest.rs +++ b/src/workspace/manifest.rs @@ -361,7 +361,8 @@ impl Manifest { .as_str() .ok_or(anyhow::anyhow!("[lib] name should be a string"))?; - let ink_metadata = self.toml + let ink_metadata = self + .toml .get("dependencies") .ok_or(anyhow::anyhow!("[dependencies] section not found"))? .get("ink_metadata") From fbe61c8d6856ae386bec44ebbe57938cd8930052 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 11:05:06 +0100 Subject: [PATCH 34/52] Return metadata path from generation --- src/cmd/metadata/mod.rs | 23 ++++++++--------------- src/main.rs | 16 +++++++++++----- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 841ba7db0..947623f4e 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -27,7 +27,10 @@ use contract::{ Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; -use std::fs; +use std::{ + fs, + path::PathBuf, +}; use url::Url; const METADATA_FILE: &str = "metadata.json"; @@ -40,15 +43,12 @@ struct GenerateMetadataCommand { } impl GenerateMetadataCommand { - pub fn exec(&self) -> Result { + pub fn exec(&self) -> Result { util::assert_channel()?; println!(" Generating metadata"); let cargo_meta = &self.crate_metadata.cargo_meta; - let out_path = cargo_meta.target_directory.join(METADATA_FILE); - let out_path_display = format!("{}", out_path.display()); - let target_dir = cargo_meta.target_directory.clone(); // build the extended contract project metadata @@ -92,10 +92,7 @@ impl GenerateMetadataCommand { .using_temp(generate_metadata)?; } - Ok(format!( - "Your metadata file is ready.\nYou can find it here:\n{}", - out_path_display - )) + Ok(out_path) } /// Generate the extended contract project metadata @@ -169,7 +166,7 @@ pub(crate) fn execute( manifest_path: ManifestPath, verbosity: Option, unstable_options: UnstableFlags, -) -> Result { +) -> Result { let crate_metadata = CrateMetadata::collect(&manifest_path)?; GenerateMetadataCommand { crate_metadata, @@ -191,13 +188,9 @@ mod tests { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let working_dir = path.join("new_project"); let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml")).unwrap(); - let message = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) + let metadata_file = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) .expect("generate metadata failed"); - println!("{}", message); - let mut metadata_file = working_dir; - metadata_file.push("target"); - metadata_file.push("metadata.json"); assert!( metadata_file.exists(), format!("Missing metadata file '{}'", metadata_file.display()) diff --git a/src/main.rs b/src/main.rs index fbbd6198e..0a8b6433a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -256,11 +256,17 @@ fn exec(cmd: Command) -> Result { Command::GenerateMetadata { verbosity, unstable_options, - } => cmd::metadata::execute( - Default::default(), - verbosity.try_into()?, - unstable_options.try_into()?, - ), + } => { + let metadata_file = cmd::metadata::execute( + Default::default(), + verbosity.try_into()?, + unstable_options.try_into()?, + )?; + Ok(format!( + "Your metadata file is ready.\nYou can find it here:\n{}", + metadata_file.display() + )) + }, Command::Test {} => Err(anyhow::anyhow!("Command unimplemented")), #[cfg(feature = "extrinsics")] Command::Deploy { From 1f3bfb4ff4d399405eb43777b8e019f45db9c995 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 11:10:51 +0100 Subject: [PATCH 35/52] Fmt --- src/cmd/metadata/mod.rs | 10 ++++------ src/main.rs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 947623f4e..ed9512969 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -27,10 +27,7 @@ use contract::{ Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler, SourceLanguage, User, }; use semver::Version; -use std::{ - fs, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; use url::Url; const METADATA_FILE: &str = "metadata.json"; @@ -188,8 +185,9 @@ mod tests { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let working_dir = path.join("new_project"); let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml")).unwrap(); - let metadata_file = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) - .expect("generate metadata failed"); + let metadata_file = + cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) + .expect("generate metadata failed"); assert!( metadata_file.exists(), diff --git a/src/main.rs b/src/main.rs index 0a8b6433a..1e721a4ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -266,7 +266,7 @@ fn exec(cmd: Command) -> Result { "Your metadata file is ready.\nYou can find it here:\n{}", metadata_file.display() )) - }, + } Command::Test {} => Err(anyhow::anyhow!("Command unimplemented")), #[cfg(feature = "extrinsics")] Command::Deploy { From b0f79658287b5329c6a25bd480722780358493ae Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 17:47:37 +0100 Subject: [PATCH 36/52] Test metadata wasm hash --- src/cmd/build.rs | 1 + src/cmd/deploy.rs | 1 + src/cmd/instantiate.rs | 1 + src/cmd/metadata/mod.rs | 31 ++++++++++++++++++++++++++++--- src/cmd/new.rs | 5 ++++- src/util.rs | 4 ++-- 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 5c7553978..134ea64f8 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -300,6 +300,7 @@ mod tests { let manifest_path = ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap(); super::execute(&manifest_path, None, UnstableFlags::default()).expect("build failed"); + Ok(()) }); } } diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index c269f1beb..7b4481a87 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -105,6 +105,7 @@ mod tests { let result = execute_deploy(&extrinsic_opts, Some(&wasm_path)); assert_matches!(result, Ok(_)); + Ok(()) }); } } diff --git a/src/cmd/instantiate.rs b/src/cmd/instantiate.rs index 20af3f628..56c3dcb7a 100644 --- a/src/cmd/instantiate.rs +++ b/src/cmd/instantiate.rs @@ -92,6 +92,7 @@ mod tests { ); assert_matches!(result, Ok(_)); + Ok(()) }); } } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index ed9512969..455c12a6b 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -176,7 +176,9 @@ pub(crate) fn execute( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; + use crate::{cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; + use std::fs; + use serde_json::{Map, Value}; #[test] fn generate_metadata() { @@ -184,15 +186,38 @@ mod tests { with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let working_dir = path.join("new_project"); - let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml")).unwrap(); + let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml"))?; + let crate_metadata = CrateMetadata::collect(&manifest_path)?; let metadata_file = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) .expect("generate metadata failed"); + let metadata_json: Map = serde_json::from_slice(&fs::read(&metadata_file)?)?; assert!( metadata_file.exists(), format!("Missing metadata file '{}'", metadata_file.display()) - ) + ); + + let source = metadata_json.get("source").expect("source not found"); + let hash = source.get("hash").expect("source.hash not found"); + + // calculate wasm hash + let wasm = fs::read(&crate_metadata.dest_wasm)?; + use ::blake2::digest::{Update as _, VariableOutput as _}; + let mut output = [0u8; 32]; + let mut blake2 = blake2::VarBlake2b::new_keyed(&[], 32); + blake2.update(wasm); + blake2.finalize_variable(|result| output.copy_from_slice(result)); + + use core::fmt::Write; + let mut expected_hash = String::new(); + write!(expected_hash, "0x").expect("failed writing to string"); + for byte in &output { + write!(expected_hash, "{:02x}", byte).expect("failed writing to string"); + } + + assert_eq!(expected_hash, hash.as_str().unwrap()); + Ok(()) }); } } diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 7c35d103b..ea6d10dc5 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -106,7 +106,8 @@ mod tests { assert_eq!( format!("{:?}", result), r#"Err(Contract names cannot contain hyphens)"# - ) + ); + Ok(()) }); } @@ -122,6 +123,7 @@ mod tests { result.err().unwrap().to_string(), "A Cargo package already exists in test_contract_cargo_project_already_exists" ); + Ok(()) }); } @@ -139,6 +141,7 @@ mod tests { result.err().unwrap().to_string(), "New contract file .gitignore already exists" ); + Ok(()) }); } } diff --git a/src/util.rs b/src/util.rs index 53ec822b0..8055f4e08 100644 --- a/src/util.rs +++ b/src/util.rs @@ -89,9 +89,9 @@ pub mod tests { use std::path::PathBuf; use tempfile::TempDir; - pub fn with_tmp_dir(f: F) { + pub fn with_tmp_dir anyhow::Result<()>>(f: F) { let tmp_dir = TempDir::new().expect("temporary directory creation failed"); - f(&tmp_dir.into_path()); + f(&tmp_dir.into_path()).unwrap() } } From 8e8408242a36aca15897e700d90a5a3a041a3b72 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 13 Jul 2020 17:48:15 +0100 Subject: [PATCH 37/52] Fmt --- src/cmd/metadata/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 455c12a6b..93067e61b 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -176,9 +176,12 @@ pub(crate) fn execute( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; - use std::fs; + use crate::{ + cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, workspace::ManifestPath, + UnstableFlags, + }; use serde_json::{Map, Value}; + use std::fs; #[test] fn generate_metadata() { @@ -191,7 +194,8 @@ mod tests { let metadata_file = cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) .expect("generate metadata failed"); - let metadata_json: Map = serde_json::from_slice(&fs::read(&metadata_file)?)?; + let metadata_json: Map = + serde_json::from_slice(&fs::read(&metadata_file)?)?; assert!( metadata_file.exists(), From 7a53a7737d7f7b9889f205f734564cdc898435ea Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 09:43:58 +0100 Subject: [PATCH 38/52] Read ink language version --- src/cmd/metadata/contract.rs | 8 +++++++- src/cmd/metadata/mod.rs | 24 +++++++++++++++--------- src/crate_metadata.rs | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index d6571f184..97d46ab8d 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -94,7 +94,13 @@ impl Serialize for SourceLanguage { where S: Serializer, { - serializer.serialize_str(&format!("{} {}", self.language, self.version)) + serializer.serialize_str(&self.to_string()) + } +} + +impl Display for SourceLanguage { + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + write!(f, "{} {}", self.language, self.version) } } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 93067e61b..4da451833 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -94,9 +94,8 @@ impl GenerateMetadataCommand { /// Generate the extended contract project metadata fn extended_metadata(&self) -> Result<(Source, Contract, Option)> { - // todo: generate these params let contract_package = &self.crate_metadata.root_package; - let ink_version = Version::new(2, 1, 0); + let ink_version = &self.crate_metadata.ink_version; let rust_version = Version::parse(&rustc_version::version()?.to_string())?; let contract_name = contract_package.name.clone(); let contract_version = Version::parse(&contract_package.version.to_string())?; @@ -114,7 +113,7 @@ impl GenerateMetadataCommand { let hash = self.wasm_hash()?; let source = { - let lang = SourceLanguage::new(Language::Ink, ink_version); + let lang = SourceLanguage::new(Language::Ink, ink_version.clone()); let compiler = SourceCompiler::new(Compiler::RustC, rust_version); Source::new(hash, lang, compiler) }; @@ -177,14 +176,18 @@ pub(crate) fn execute( #[cfg(test)] mod tests { use crate::{ - cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, workspace::ManifestPath, + cmd::{self, metadata::contract::*}, + crate_metadata::CrateMetadata, + util::tests::with_tmp_dir, + workspace::ManifestPath, UnstableFlags, }; + use blake2::digest::{Update as _, VariableOutput as _}; use serde_json::{Map, Value}; - use std::fs; + use std::{fmt::Write, fs}; #[test] - fn generate_metadata() { + fn generate_metadata() -> anyhow::Result<()> { env_logger::try_init().ok(); with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); @@ -204,24 +207,27 @@ mod tests { let source = metadata_json.get("source").expect("source not found"); let hash = source.get("hash").expect("source.hash not found"); + let language = source.get("language").expect("source.language not found"); // calculate wasm hash let wasm = fs::read(&crate_metadata.dest_wasm)?; - use ::blake2::digest::{Update as _, VariableOutput as _}; let mut output = [0u8; 32]; let mut blake2 = blake2::VarBlake2b::new_keyed(&[], 32); blake2.update(wasm); blake2.finalize_variable(|result| output.copy_from_slice(result)); - use core::fmt::Write; let mut expected_hash = String::new(); write!(expected_hash, "0x").expect("failed writing to string"); for byte in &output { write!(expected_hash, "{:02x}", byte).expect("failed writing to string"); } + let expected_language = + SourceLanguage::new(Language::Ink, crate_metadata.ink_version).to_string(); assert_eq!(expected_hash, hash.as_str().unwrap()); + assert_eq!(expected_language, language.as_str().unwrap()); + Ok(()) - }); + }) } } diff --git a/src/crate_metadata.rs b/src/crate_metadata.rs index d077cb3c5..cc312e3e8 100644 --- a/src/crate_metadata.rs +++ b/src/crate_metadata.rs @@ -17,6 +17,7 @@ use crate::workspace::ManifestPath; use anyhow::{Context, Result}; use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package}; +use semver::Version; use serde_json::{Map, Value}; use std::{fs, path::PathBuf}; use toml::value; @@ -31,6 +32,7 @@ pub struct CrateMetadata { pub root_package: Package, pub original_wasm: PathBuf, pub dest_wasm: PathBuf, + pub ink_version: Version, pub documentation: Option, pub homepage: Option, pub user: Option>, @@ -56,6 +58,21 @@ impl CrateMetadata { dest_wasm.push(package_name.clone()); dest_wasm.set_extension("wasm"); + let ink_version = metadata + .packages + .iter() + .find_map(|package| { + if package.name == "ink_lang" { + Some( + Version::parse(&package.version.to_string()) + .expect("Invalid ink_lang version string"), + ) + } else { + None + } + }) + .ok_or(anyhow::anyhow!("No 'ink_lang' dependency found"))?; + let (documentation, homepage, user) = get_cargo_toml_metadata(manifest_path)?; let crate_metadata = CrateMetadata { @@ -65,6 +82,7 @@ impl CrateMetadata { package_name, original_wasm, dest_wasm, + ink_version, documentation, homepage, user, From 9516ec43824561791526fc6c88b4c5f94e067370 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 09:45:16 +0100 Subject: [PATCH 39/52] Ensure tmp dir is cleaned up after build --- Cargo.toml | 3 +++ src/cmd/build.rs | 6 +++--- src/cmd/deploy.rs | 4 ++-- src/cmd/instantiate.rs | 4 ++-- src/cmd/new.rs | 21 ++++++++++++--------- src/util.rs | 31 ++++++++++++++++++++++++++++--- 6 files changed, 50 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e2dba657..683b0772a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,3 +59,6 @@ wabt = "0.9.2" default = [] extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex"] test-ci-only = [] + +[profile.dev] +panic = "unwind" diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 134ea64f8..d31d3f668 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -291,16 +291,16 @@ pub(crate) fn execute_with_metadata( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; + use crate::{cmd, util::tests::{with_tmp_dir}, workspace::ManifestPath, UnstableFlags}; #[test] - fn build_template() { + fn build_template() -> anyhow::Result<()> { with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap(); super::execute(&manifest_path, None, UnstableFlags::default()).expect("build failed"); Ok(()) - }); + }) } } diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index 7b4481a87..e0f9d4c04 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -88,7 +88,7 @@ mod tests { #[test] #[ignore] // depends on a local substrate node running - fn deploy_contract() { + fn deploy_contract() -> anyhow::Result<()> { with_tmp_dir(|path| { let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); @@ -106,6 +106,6 @@ mod tests { assert_matches!(result, Ok(_)); Ok(()) - }); + }) } } diff --git a/src/cmd/instantiate.rs b/src/cmd/instantiate.rs index 56c3dcb7a..8bf0e2ea8 100644 --- a/src/cmd/instantiate.rs +++ b/src/cmd/instantiate.rs @@ -65,7 +65,7 @@ mod tests { #[test] #[ignore] // depends on a local substrate node running - fn instantiate_contract() { + fn instantiate_contract() -> anyhow::Result<()> { with_tmp_dir(|path| { let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); @@ -93,6 +93,6 @@ mod tests { assert_matches!(result, Ok(_)); Ok(()) - }); + }) } } diff --git a/src/cmd/new.rs b/src/cmd/new.rs index ea6d10dc5..496dcac68 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -17,18 +17,21 @@ use std::{ env, fs, io::{Cursor, Read, Seek, SeekFrom, Write}, - path::PathBuf, + path::Path, }; use anyhow::Result; use heck::CamelCase as _; -pub(crate) fn execute(name: &str, dir: Option<&PathBuf>) -> Result { +pub(crate) fn execute

(name: &str, dir: Option

) -> Result +where + P: AsRef +{ if name.contains('-') { anyhow::bail!("Contract names cannot contain hyphens"); } - let out_dir = dir.unwrap_or(&env::current_dir()?).join(name); + let out_dir = dir.map_or(env::current_dir()?, |p| p.as_ref().to_path_buf()).join(name); if out_dir.join("Cargo.toml").exists() { anyhow::bail!("A Cargo package already exists in {}", name); } @@ -100,7 +103,7 @@ mod tests { use crate::{cmd, util::tests::with_tmp_dir}; #[test] - fn rejects_hyphenated_name() { + fn rejects_hyphenated_name() -> anyhow::Result<()> { with_tmp_dir(|path| { let result = cmd::new::execute("rejects-hyphenated-name", Some(path)); assert_eq!( @@ -108,11 +111,11 @@ mod tests { r#"Err(Contract names cannot contain hyphens)"# ); Ok(()) - }); + }) } #[test] - fn contract_cargo_project_already_exists() { + fn contract_cargo_project_already_exists() -> anyhow::Result<()> { with_tmp_dir(|path| { let name = "test_contract_cargo_project_already_exists"; let _ = execute(name, Some(path)); @@ -124,11 +127,11 @@ mod tests { "A Cargo package already exists in test_contract_cargo_project_already_exists" ); Ok(()) - }); + }) } #[test] - fn dont_overwrite_existing_files_not_in_cargo_project() { + fn dont_overwrite_existing_files_not_in_cargo_project() -> anyhow::Result<()> { with_tmp_dir(|path| { let name = "dont_overwrite_existing_files"; let dir = path.join(name); @@ -142,6 +145,6 @@ mod tests { "New contract file .gitignore already exists" ); Ok(()) - }); + }) } } diff --git a/src/util.rs b/src/util.rs index 8055f4e08..28ea7a8d0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -86,12 +86,37 @@ where #[cfg(test)] pub mod tests { - use std::path::PathBuf; + use std::{ + panic, + path::Path + }; use tempfile::TempDir; - pub fn with_tmp_dir anyhow::Result<()>>(f: F) { + pub fn with_tmp_dir(f: F) -> anyhow::Result<()> + where + F: FnOnce(&Path) -> anyhow::Result<()> + panic::UnwindSafe + { let tmp_dir = TempDir::new().expect("temporary directory creation failed"); - f(&tmp_dir.into_path()).unwrap() + // catch test panics in order to clean up temp dir which will be very large + let result = std::panic::catch_unwind(|| { + f(tmp_dir.path()).expect("Error executing test with tmp dir"); + + // explicitly clean up temp dir + tmp_dir.close().expect("Error cleaning up tmp dir") + }); + + if let Err(panic) = result { + match panic.downcast::() { + Ok(panic_msg) => { + anyhow::bail!("{}", panic_msg); + } + Err(_) => { + anyhow::bail!("test panic happened: unknown type."); + } + } + } + + Ok(()) } } From 04e334adc3a0711576684f23a2ad31a0685b8224 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 09:49:41 +0100 Subject: [PATCH 40/52] Make cargo-contract tmp dir not hidden --- src/workspace/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index 8df3d14bf..6489a126e 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -162,7 +162,7 @@ impl Workspace { F: FnOnce(&ManifestPath) -> Result<()>, { let tmp_dir = tempfile::Builder::new() - .prefix(".cargo-contract_") + .prefix("cargo-contract_") .tempdir()?; log::debug!("Using temp workspace at '{}'", tmp_dir.path().display()); let new_paths = self.write(&tmp_dir)?; From 6eef8be9a2ea4b13a17383d60cc7a44a0756d7c6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 09:56:17 +0100 Subject: [PATCH 41/52] Fmt --- src/cmd/build.rs | 2 +- src/cmd/new.rs | 6 ++++-- src/util.rs | 7 ++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index d31d3f668..6d5446093 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -291,7 +291,7 @@ pub(crate) fn execute_with_metadata( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{cmd, util::tests::{with_tmp_dir}, workspace::ManifestPath, UnstableFlags}; + use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; #[test] fn build_template() -> anyhow::Result<()> { diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 496dcac68..4e4457262 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -25,13 +25,15 @@ use heck::CamelCase as _; pub(crate) fn execute

(name: &str, dir: Option

) -> Result where - P: AsRef + P: AsRef, { if name.contains('-') { anyhow::bail!("Contract names cannot contain hyphens"); } - let out_dir = dir.map_or(env::current_dir()?, |p| p.as_ref().to_path_buf()).join(name); + let out_dir = dir + .map_or(env::current_dir()?, |p| p.as_ref().to_path_buf()) + .join(name); if out_dir.join("Cargo.toml").exists() { anyhow::bail!("A Cargo package already exists in {}", name); } diff --git a/src/util.rs b/src/util.rs index 28ea7a8d0..00af161e0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -86,15 +86,12 @@ where #[cfg(test)] pub mod tests { - use std::{ - panic, - path::Path - }; + use std::{panic, path::Path}; use tempfile::TempDir; pub fn with_tmp_dir(f: F) -> anyhow::Result<()> where - F: FnOnce(&Path) -> anyhow::Result<()> + panic::UnwindSafe + F: FnOnce(&Path) -> anyhow::Result<()> + panic::UnwindSafe, { let tmp_dir = TempDir::new().expect("temporary directory creation failed"); From f6855dd1924b484d149233ff66f22dfed89c1025 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 10:08:54 +0100 Subject: [PATCH 42/52] Test source compiler, contract name, contract version --- src/cmd/metadata/contract.rs | 8 +++++++- src/cmd/metadata/mod.rs | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 97d46ab8d..3d3696d4b 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -129,12 +129,18 @@ pub struct SourceCompiler { version: Version, } +impl Display for SourceCompiler { + fn fmt(&self, f: &mut Formatter<'_>) -> DisplayResult { + write!(f, "{} {}", self.compiler, self.version) + } +} + impl Serialize for SourceCompiler { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&format!("{} {}", self.compiler, self.version)) + serializer.serialize_str(&self.to_string()) } } diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 4da451833..9ae010e91 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -208,6 +208,11 @@ mod tests { let source = metadata_json.get("source").expect("source not found"); let hash = source.get("hash").expect("source.hash not found"); let language = source.get("language").expect("source.language not found"); + let compiler = source.get("compiler").expect("source.compiler not found"); + + let contract = metadata_json.get("contract").expect("contract not found"); + let name = contract.get("name").expect("contract.name not found"); + let version = contract.get("version").expect("contract.version not found"); // calculate wasm hash let wasm = fs::read(&crate_metadata.dest_wasm)?; @@ -223,9 +228,16 @@ mod tests { } let expected_language = SourceLanguage::new(Language::Ink, crate_metadata.ink_version).to_string(); + let expected_rustc_version = + semver::Version::parse(&rustc_version::version()?.to_string())?; + let expected_compiler = + SourceCompiler::new(Compiler::RustC, expected_rustc_version).to_string(); assert_eq!(expected_hash, hash.as_str().unwrap()); assert_eq!(expected_language, language.as_str().unwrap()); + assert_eq!(expected_compiler, compiler.as_str().unwrap()); + assert_eq!(crate_metadata.package_name, name.as_str().unwrap()); + assert_eq!(crate_metadata.root_package.version.to_string(), version.as_str().unwrap()); Ok(()) }) From c3406c7e6943e495934fb280ccfab1f89ed9c9e6 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 11:23:55 +0100 Subject: [PATCH 43/52] Use ink branch temporarily --- templates/new/_Cargo.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index df71d79d9..c5fa09b02 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -5,10 +5,11 @@ authors = ["[your_name] <[your_email]>"] edition = "2018" [dependencies] -ink_metadata = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } -ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } -ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } -ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } +# todo: once https://github.com/paritytech/ink/pull/467 is merged change this back to master +ink_metadata = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } +ink_primitives = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", default-features = false } +ink_core = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_core", default-features = false } +ink_lang = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_lang", default-features = false } scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } From f2b18a3ee029361a5c796819bb42ad0b601fbeb4 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 11:42:39 +0100 Subject: [PATCH 44/52] Test for authors and documentation --- src/cmd/metadata/mod.rs | 51 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 9ae010e91..6721c05bd 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -185,6 +185,37 @@ mod tests { use blake2::digest::{Update as _, VariableOutput as _}; use serde_json::{Map, Value}; use std::{fmt::Write, fs}; + use toml::value; + + struct TestContractManifest { + toml: value::Table, + manifest_path: ManifestPath, + } + + impl TestContractManifest { + fn new(manifest_path: ManifestPath) -> anyhow::Result { + Ok(Self { + toml: toml::from_slice(&fs::read(&manifest_path)?)?, + manifest_path + }) + } + + fn add_package_value(&mut self, key: &'static str, value: value::Value) -> anyhow::Result<()> { + self + .toml + .get_mut("package") + .ok_or(anyhow::anyhow!("package section not found"))? + .as_table_mut() + .ok_or(anyhow::anyhow!("package section should be a table"))? + .insert(key.into(), value); + Ok(()) + } + + fn write(&self) -> anyhow::Result<()> { + let toml = toml::to_string(&self.toml)?; + fs::write(&self.manifest_path, toml).map_err(Into::into) + } + } #[test] fn generate_metadata() -> anyhow::Result<()> { @@ -193,9 +224,15 @@ mod tests { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let working_dir = path.join("new_project"); let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml"))?; - let crate_metadata = CrateMetadata::collect(&manifest_path)?; + + // add optional metadata fields + let mut test_manifest = TestContractManifest::new(manifest_path)?; + test_manifest.add_package_value("description", "contract description".into())?; + test_manifest.write()?; + + let crate_metadata = CrateMetadata::collect(&test_manifest.manifest_path)?; let metadata_file = - cmd::metadata::execute(manifest_path, None, UnstableFlags::default()) + cmd::metadata::execute(test_manifest.manifest_path, None, UnstableFlags::default()) .expect("generate metadata failed"); let metadata_json: Map = serde_json::from_slice(&fs::read(&metadata_file)?)?; @@ -213,6 +250,14 @@ mod tests { let contract = metadata_json.get("contract").expect("contract not found"); let name = contract.get("name").expect("contract.name not found"); let version = contract.get("version").expect("contract.version not found"); + let authors = contract.get("authors") + .expect("contract.authors not found") + .as_array() + .expect("contract.authors is an array") + .iter() + .map(|author| author.as_str().expect("author is a string")) + .collect::>(); + let description = contract.get("description").expect("contract.description not found"); // calculate wasm hash let wasm = fs::read(&crate_metadata.dest_wasm)?; @@ -238,6 +283,8 @@ mod tests { assert_eq!(expected_compiler, compiler.as_str().unwrap()); assert_eq!(crate_metadata.package_name, name.as_str().unwrap()); assert_eq!(crate_metadata.root_package.version.to_string(), version.as_str().unwrap()); + assert_eq!(crate_metadata.root_package.authors, authors); + assert_eq!("contract description", description.as_str().unwrap()); Ok(()) }) From 4148a33c1393c9bf483216d139dba47d92c4659c Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 14 Jul 2020 12:38:39 +0100 Subject: [PATCH 45/52] Test user provided metadata section --- src/cmd/metadata/mod.rs | 91 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 6721c05bd..aa46b6720 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -196,21 +196,54 @@ mod tests { fn new(manifest_path: ManifestPath) -> anyhow::Result { Ok(Self { toml: toml::from_slice(&fs::read(&manifest_path)?)?, - manifest_path + manifest_path, }) } - fn add_package_value(&mut self, key: &'static str, value: value::Value) -> anyhow::Result<()> { - self - .toml + fn package_mut(&mut self) -> anyhow::Result<&mut value::Table> { + self.toml .get_mut("package") .ok_or(anyhow::anyhow!("package section not found"))? .as_table_mut() - .ok_or(anyhow::anyhow!("package section should be a table"))? + .ok_or(anyhow::anyhow!("package section should be a table")) + } + + /// Add a key/value to the `[package.metadata.contract.user]` section + fn add_user_metadata_value( + &mut self, + key: &'static str, + value: value::Value, + ) -> anyhow::Result<()> { + self.package_mut()? + .entry("metadata") + .or_insert(value::Value::Table(Default::default())) + .as_table_mut() + .ok_or(anyhow::anyhow!("metadata section should be a table"))? + .entry("contract") + .or_insert(value::Value::Table(Default::default())) + .as_table_mut() + .ok_or(anyhow::anyhow!( + "metadata.contract section should be a table" + ))? + .entry("user") + .or_insert(value::Value::Table(Default::default())) + .as_table_mut() + .ok_or(anyhow::anyhow!( + "metadata.contract.user section should be a table" + ))? .insert(key.into(), value); Ok(()) } + fn add_package_value( + &mut self, + key: &'static str, + value: value::Value, + ) -> anyhow::Result<()> { + self.package_mut()?.insert(key.into(), value); + Ok(()) + } + fn write(&self) -> anyhow::Result<()> { let toml = toml::to_string(&self.toml)?; fs::write(&self.manifest_path, toml).map_err(Into::into) @@ -228,6 +261,16 @@ mod tests { // add optional metadata fields let mut test_manifest = TestContractManifest::new(manifest_path)?; test_manifest.add_package_value("description", "contract description".into())?; + test_manifest.add_package_value("documentation", "http://documentation.com".into())?; + test_manifest.add_package_value("repository", "http://repository.com".into())?; + test_manifest.add_package_value("homepage", "http://homepage.com".into())?; + test_manifest.add_package_value("license", "Apache-2.0".into())?; + test_manifest + .add_user_metadata_value("some-user-provided-field", "and-its-value".into())?; + test_manifest.add_user_metadata_value( + "more-user-provided-fields", + vec!["and", "their", "values"].into(), + )?; test_manifest.write()?; let crate_metadata = CrateMetadata::collect(&test_manifest.manifest_path)?; @@ -250,14 +293,29 @@ mod tests { let contract = metadata_json.get("contract").expect("contract not found"); let name = contract.get("name").expect("contract.name not found"); let version = contract.get("version").expect("contract.version not found"); - let authors = contract.get("authors") + let authors = contract + .get("authors") .expect("contract.authors not found") .as_array() .expect("contract.authors is an array") .iter() .map(|author| author.as_str().expect("author is a string")) .collect::>(); - let description = contract.get("description").expect("contract.description not found"); + let description = contract + .get("description") + .expect("contract.description not found"); + let documentation = contract + .get("documentation") + .expect("contract.documentation not found"); + let repository = contract + .get("repository") + .expect("contract.repository not found"); + let homepage = contract + .get("homepage") + .expect("contract.homepage not found"); + let license = contract.get("license").expect("contract.license not found"); + + let user = metadata_json.get("user").expect("user section not found"); // calculate wasm hash let wasm = fs::read(&crate_metadata.dest_wasm)?; @@ -277,14 +335,31 @@ mod tests { semver::Version::parse(&rustc_version::version()?.to_string())?; let expected_compiler = SourceCompiler::new(Compiler::RustC, expected_rustc_version).to_string(); + let mut expected_user_metadata = serde_json::Map::new(); + expected_user_metadata + .insert("some-user-provided-field".into(), "and-its-value".into()); + expected_user_metadata.insert( + "more-user-provided-fields".into(), + serde_json::Value::Array( + vec!["and".into(), "their".into(), "values".into()].into(), + ), + ); assert_eq!(expected_hash, hash.as_str().unwrap()); assert_eq!(expected_language, language.as_str().unwrap()); assert_eq!(expected_compiler, compiler.as_str().unwrap()); assert_eq!(crate_metadata.package_name, name.as_str().unwrap()); - assert_eq!(crate_metadata.root_package.version.to_string(), version.as_str().unwrap()); + assert_eq!( + crate_metadata.root_package.version.to_string(), + version.as_str().unwrap() + ); assert_eq!(crate_metadata.root_package.authors, authors); assert_eq!("contract description", description.as_str().unwrap()); + assert_eq!("http://documentation.com/", documentation.as_str().unwrap()); + assert_eq!("http://repository.com/", repository.as_str().unwrap()); + assert_eq!("http://homepage.com/", homepage.as_str().unwrap()); + assert_eq!("Apache-2.0", license.as_str().unwrap()); + assert_eq!(&expected_user_metadata, user.as_object().unwrap()); Ok(()) }) From e40c25f3a2233c00caafa2ee857b6805ecbd33ae Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 16 Jul 2020 16:18:20 +0100 Subject: [PATCH 46/52] Drop automatically handles tempdir removal --- Cargo.toml | 3 --- src/cmd/build.rs | 2 +- src/cmd/deploy.rs | 2 +- src/cmd/instantiate.rs | 2 +- src/cmd/metadata/mod.rs | 2 +- src/cmd/new.rs | 6 +++--- src/util.rs | 28 ++++++---------------------- 7 files changed, 13 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 683b0772a..1e2dba657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,3 @@ wabt = "0.9.2" default = [] extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex"] test-ci-only = [] - -[profile.dev] -panic = "unwind" diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 6d5446093..7848b3013 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -294,7 +294,7 @@ mod tests { use crate::{cmd, util::tests::with_tmp_dir, workspace::ManifestPath, UnstableFlags}; #[test] - fn build_template() -> anyhow::Result<()> { + fn build_template() { with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = diff --git a/src/cmd/deploy.rs b/src/cmd/deploy.rs index e0f9d4c04..11fbc1215 100644 --- a/src/cmd/deploy.rs +++ b/src/cmd/deploy.rs @@ -88,7 +88,7 @@ mod tests { #[test] #[ignore] // depends on a local substrate node running - fn deploy_contract() -> anyhow::Result<()> { + fn deploy_contract() { with_tmp_dir(|path| { let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); diff --git a/src/cmd/instantiate.rs b/src/cmd/instantiate.rs index 8bf0e2ea8..32577dda2 100644 --- a/src/cmd/instantiate.rs +++ b/src/cmd/instantiate.rs @@ -65,7 +65,7 @@ mod tests { #[test] #[ignore] // depends on a local substrate node running - fn instantiate_contract() -> anyhow::Result<()> { + fn instantiate_contract() { with_tmp_dir(|path| { let wasm = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index aa46b6720..96b6c53a5 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -251,7 +251,7 @@ mod tests { } #[test] - fn generate_metadata() -> anyhow::Result<()> { + fn generate_metadata() { env_logger::try_init().ok(); with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); diff --git a/src/cmd/new.rs b/src/cmd/new.rs index 4e4457262..39d794530 100644 --- a/src/cmd/new.rs +++ b/src/cmd/new.rs @@ -105,7 +105,7 @@ mod tests { use crate::{cmd, util::tests::with_tmp_dir}; #[test] - fn rejects_hyphenated_name() -> anyhow::Result<()> { + fn rejects_hyphenated_name() { with_tmp_dir(|path| { let result = cmd::new::execute("rejects-hyphenated-name", Some(path)); assert_eq!( @@ -117,7 +117,7 @@ mod tests { } #[test] - fn contract_cargo_project_already_exists() -> anyhow::Result<()> { + fn contract_cargo_project_already_exists() { with_tmp_dir(|path| { let name = "test_contract_cargo_project_already_exists"; let _ = execute(name, Some(path)); @@ -133,7 +133,7 @@ mod tests { } #[test] - fn dont_overwrite_existing_files_not_in_cargo_project() -> anyhow::Result<()> { + fn dont_overwrite_existing_files_not_in_cargo_project() { with_tmp_dir(|path| { let name = "dont_overwrite_existing_files"; let dir = path.join(name); diff --git a/src/util.rs b/src/util.rs index 00af161e0..9f0428169 100644 --- a/src/util.rs +++ b/src/util.rs @@ -87,33 +87,17 @@ where #[cfg(test)] pub mod tests { use std::{panic, path::Path}; - use tempfile::TempDir; - pub fn with_tmp_dir(f: F) -> anyhow::Result<()> + pub fn with_tmp_dir(f: F) where F: FnOnce(&Path) -> anyhow::Result<()> + panic::UnwindSafe, { - let tmp_dir = TempDir::new().expect("temporary directory creation failed"); + let tmp_dir = tempfile::Builder::new() + .prefix("cargo-contract.test.") + .tempdir() + .expect("temporary directory creation failed"); // catch test panics in order to clean up temp dir which will be very large - let result = std::panic::catch_unwind(|| { - f(tmp_dir.path()).expect("Error executing test with tmp dir"); - - // explicitly clean up temp dir - tmp_dir.close().expect("Error cleaning up tmp dir") - }); - - if let Err(panic) = result { - match panic.downcast::() { - Ok(panic_msg) => { - anyhow::bail!("{}", panic_msg); - } - Err(_) => { - anyhow::bail!("test panic happened: unknown type."); - } - } - } - - Ok(()) + f(tmp_dir.path()).expect("Error executing test with tmp dir") } } From a7dad2383f7b4b94592924eceb655f79109af14d Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Thu, 16 Jul 2020 16:19:12 +0100 Subject: [PATCH 47/52] Remove redundant unwind bound --- src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.rs b/src/util.rs index 9f0428169..84b2b8948 100644 --- a/src/util.rs +++ b/src/util.rs @@ -86,11 +86,11 @@ where #[cfg(test)] pub mod tests { - use std::{panic, path::Path}; + use std::path::Path; pub fn with_tmp_dir(f: F) where - F: FnOnce(&Path) -> anyhow::Result<()> + panic::UnwindSafe, + F: FnOnce(&Path) -> anyhow::Result<()>, { let tmp_dir = tempfile::Builder::new() .prefix("cargo-contract.test.") From b3234961ad4ed2e721368384c650f5475aaefe19 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 21 Jul 2020 14:26:47 +0100 Subject: [PATCH 48/52] Comment crate features --- Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1e2dba657..9a676db88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,5 +57,13 @@ wabt = "0.9.2" [features] default = [] + +# Enable this for (experimental) commands to deploy, instantiate and call contracts. +# +# Disabled by default extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex"] + +# Enable this to execute long running tests, which usually are only run on the CI server +# +# Disabled by default test-ci-only = [] From 04ade5724cd5259e6cb77c32c41aea8117d45df9 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 21 Jul 2020 14:30:27 +0100 Subject: [PATCH 49/52] Revert template to point at ink master --- templates/new/_Cargo.toml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml index c5fa09b02..df71d79d9 100644 --- a/templates/new/_Cargo.toml +++ b/templates/new/_Cargo.toml @@ -5,11 +5,10 @@ authors = ["[your_name] <[your_email]>"] edition = "2018" [dependencies] -# todo: once https://github.com/paritytech/ink/pull/467 is merged change this back to master -ink_metadata = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } -ink_primitives = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", default-features = false } -ink_core = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_core", default-features = false } -ink_lang = { git = "https://github.com/paritytech/ink", branch = "aj-extra-metadata", package = "ink_lang", default-features = false } +ink_metadata = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_metadata", default-features = false, features = ["derive"], optional = true } +ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } +ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } +ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } scale-info = { version = "0.3", default-features = false, features = ["derive"], optional = true } From 5d0ce9abf363a7f20c3ef6b6cede4226ec4d78f0 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 21 Jul 2020 14:58:50 +0100 Subject: [PATCH 50/52] Self --- src/cmd/metadata/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 3d3696d4b..1eb674a7f 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -46,7 +46,7 @@ impl ContractMetadata { let metadata_version = semver::Version::parse(METADATA_VERSION) .expect("METADATA_VERSION is a valid semver string"); - ContractMetadata { + Self { metadata_version, source, contract, From df03f97748b3d1770477d35c89169a200fb7d651 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 21 Jul 2020 14:59:48 +0100 Subject: [PATCH 51/52] Update comment --- src/cmd/metadata/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/metadata/contract.rs b/src/cmd/metadata/contract.rs index 1eb674a7f..dc273db09 100644 --- a/src/cmd/metadata/contract.rs +++ b/src/cmd/metadata/contract.rs @@ -36,7 +36,7 @@ pub struct ContractMetadata { } impl ContractMetadata { - /// Construct a new ContractMetadata + /// Construct new contract metadata pub fn new( source: Source, contract: Contract, From 5bcc24d78c1be054e865a97fb61ed599d61907f1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Tue, 21 Jul 2020 15:02:07 +0100 Subject: [PATCH 52/52] Remove redundant comment --- src/cmd/metadata/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/metadata/mod.rs b/src/cmd/metadata/mod.rs index 96b6c53a5..9a62dc046 100644 --- a/src/cmd/metadata/mod.rs +++ b/src/cmd/metadata/mod.rs @@ -61,7 +61,6 @@ impl GenerateMetadataCommand { &manifest_path.cargo_arg(), &target_dir_arg, "--release", - // "--no-default-features", // Breaks builds for MacOS (linker errors), we should investigate this issue asap! ], self.crate_metadata.manifest_path.directory(), self.verbosity,