diff --git a/Cargo.lock b/Cargo.lock index 63cfa8c2a..e39a73d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,6 +771,8 @@ dependencies = [ "parity-scale-codec", "parity-wasm", "pretty_assertions", + "proc-macro2", + "quote", "rustc_version", "semver", "serde", diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index ab26e9302..c5c5f2373 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -27,6 +27,8 @@ scale = { package = "parity-scale-codec", version = "3.0.0", features = ["derive toml = "0.7.3" tracing = "0.1.37" parity-wasm = "0.45.0" +proc-macro2 = "1" +quote = "1" semver = { version = "1.0.17", features = ["serde"] } serde = { version = "1", default-features = false, features = ["derive"] } serde_json = "1.0.96" diff --git a/crates/build/src/metadata.rs b/crates/build/src/metadata.rs index 28db8ca84..2ca6a75b1 100644 --- a/crates/build/src/metadata.rs +++ b/crates/build/src/metadata.rs @@ -55,6 +55,8 @@ use std::{ }; use url::Url; +const INK_EVENT_METADATA_SECTION_PREFIX: &str = "__ink_event_metadata_"; + /// Artifacts resulting from metadata generation. #[derive(serde::Serialize)] pub struct MetadataArtifacts { @@ -180,6 +182,22 @@ pub(crate) fn execute( Ok(()) }; + let module: parity_wasm::elements::Module = + parity_wasm::deserialize_file(&crate_metadata.original_wasm)?; + let ink_event_metadata_externs = module + .custom_sections() + .filter_map(|section| { + if section + .name() + .starts_with(INK_EVENT_METADATA_SECTION_PREFIX) + { + Some(section.name().to_owned()) + } else { + None + } + }) + .collect::>(); + if unstable_options.original_manifest { generate_metadata(&crate_metadata.manifest_path)?; } else { @@ -192,6 +210,7 @@ pub(crate) fn execute( })? .with_metadata_gen_package( crate_metadata.manifest_path.absolute_directory()?, + ink_event_metadata_externs, )? .using_temp(generate_metadata)?; } diff --git a/crates/build/src/workspace/manifest.rs b/crates/build/src/workspace/manifest.rs index d0afd7d97..35f3e7dee 100644 --- a/crates/build/src/workspace/manifest.rs +++ b/crates/build/src/workspace/manifest.rs @@ -20,7 +20,7 @@ use anyhow::{ }; use super::{ - metadata, + metadata::MetadataPackage, Profile, }; use crate::OptimizationPasses; @@ -124,7 +124,7 @@ pub struct Manifest { path: ManifestPath, toml: value::Table, /// True if a metadata package should be generated for this manifest - metadata_package: bool, + metadata_package: Option, } impl Manifest { @@ -138,7 +138,7 @@ impl Manifest { Ok(Manifest { path: manifest_path, toml, - metadata_package: false, + metadata_package: None, }) } @@ -269,7 +269,10 @@ impl Manifest { } /// Adds a metadata package to the manifest workspace for generating metadata - pub fn with_metadata_package(&mut self) -> Result<&mut Self> { + pub fn with_metadata_package( + &mut self, + ink_event_metadata_externs: Vec, + ) -> Result<&mut Self> { let workspace = self .toml .entry("workspace") @@ -298,7 +301,20 @@ impl Manifest { members.push(METADATA_PACKAGE_PATH.into()); } - self.metadata_package = true; + let contract_package_name = self + .toml + .get("package") + .ok_or_else(|| anyhow::anyhow!("package section not found"))? + .get("name") + .ok_or_else(|| anyhow::anyhow!("[package] name field not found"))? + .as_str() + .ok_or_else(|| anyhow::anyhow!("[package] name should be a string"))? + .to_owned(); + + self.metadata_package = Some(MetadataPackage::new( + contract_package_name, + ink_event_metadata_externs, + )); Ok(self) } @@ -359,7 +375,7 @@ impl Manifest { .context(format!("Creating directory '{}'", dir.display()))?; } - if self.metadata_package { + if let Some(metadata_package) = &self.metadata_package { let dir = if let Some(manifest_dir) = manifest_path.directory() { manifest_dir.join(METADATA_PACKAGE_PATH) } else { @@ -369,15 +385,6 @@ impl Manifest { fs::create_dir_all(&dir) .context(format!("Creating directory '{}'", dir.display()))?; - let contract_package_name = self - .toml - .get("package") - .ok_or_else(|| anyhow::anyhow!("package section not found"))? - .get("name") - .ok_or_else(|| anyhow::anyhow!("[package] name field not found"))? - .as_str() - .ok_or_else(|| anyhow::anyhow!("[package] name should be a string"))?; - let ink_crate = self .toml .get("dependencies") @@ -394,12 +401,7 @@ impl Manifest { .as_table() .ok_or_else(|| anyhow::anyhow!("[features] section should be a table"))?; - metadata::generate_package( - dir, - contract_package_name, - ink_crate.clone(), - features, - )?; + metadata_package.generate(dir, ink_crate.clone(), features)?; } let updated_toml = toml::to_string(&self.toml)?; diff --git a/crates/build/src/workspace/metadata.rs b/crates/build/src/workspace/metadata.rs index 5ab27e03f..eb242c4a3 100644 --- a/crates/build/src/workspace/metadata.rs +++ b/crates/build/src/workspace/metadata.rs @@ -24,70 +24,130 @@ use toml::{ Value, }; -/// Generates a cargo workspace package `metadata-gen` which will be invoked via `cargo run` to -/// generate contract metadata. -/// -/// # Note -/// -/// `ink!` dependencies are copied from the containing contract workspace to ensure the same -/// versions are utilized. -pub(super) fn generate_package>( - target_dir: P, - contract_package_name: &str, - mut ink_crate_dependency: Table, - contract_features: &Table, -) -> Result<()> { - let dir = target_dir.as_ref(); - tracing::debug!( - "Generating metadata package for {} in {}", - contract_package_name, - dir.display() - ); - - let cargo_toml = include_str!("../../templates/generate-metadata/_Cargo.toml"); - let main_rs = include_str!("../../templates/generate-metadata/main.rs"); - - let mut cargo_toml: 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 contract dependency - let contract = deps - .get_mut("contract") - .expect("contract dependency specified in the template") - .as_table_mut() - .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_crate_dependency.remove("default-features"); - ink_crate_dependency.remove("features"); - ink_crate_dependency.remove("optional"); - - // add ink dependencies copied from contract manifest - deps.insert("ink".into(), ink_crate_dependency.into()); - - // add features from contract - let features = cargo_toml - .entry("features") - .or_insert(Value::Table(Default::default())) - .as_table_mut() - .ok_or_else(|| anyhow::anyhow!("features should be a table"))?; - - for (feature, _) in contract_features { - if feature != "default" && feature != "std" { - features.insert( - feature.to_string(), - Value::Array(vec![format!("contract/{feature}").into()]), - ); +/// Info for generating a metadata package. +pub struct MetadataPackage { + contract_package_name: String, + ink_event_metadata_externs: Vec, +} + +impl MetadataPackage { + /// Construct a new [`MetadataPackage`]. + pub fn new( + contract_package_name: String, + ink_event_metadata_externs: Vec, + ) -> Self { + Self { + ink_event_metadata_externs, + contract_package_name, } } - let cargo_toml = toml::to_string(&cargo_toml)?; - fs::write(dir.join("Cargo.toml"), cargo_toml)?; - fs::write(dir.join("main.rs"), main_rs)?; - Ok(()) + /// Generates a cargo workspace package `metadata-gen` which will be invoked via `cargo run` to + /// generate contract metadata. + /// + /// # Note + /// + /// `ink!` dependencies are copied from the containing contract workspace to ensure the same + /// versions are utilized. + pub fn generate>( + &self, + target_dir: P, + mut ink_crate_dependency: Table, + contract_features: &Table, + ) -> Result<()> { + let dir = target_dir.as_ref(); + tracing::debug!( + "Generating metadata package for {} in {}", + self.contract_package_name, + dir.display() + ); + + let cargo_toml = include_str!("../../templates/generate-metadata/_Cargo.toml"); + let main_rs = self.generate_main(); + + let mut cargo_toml: 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 contract dependency + let contract = deps + .get_mut("contract") + .expect("contract dependency specified in the template") + .as_table_mut() + .expect("contract dependency is a table specified in the template"); + contract.insert("package".into(), self.contract_package_name.clone().into()); + + // make ink_metadata dependency use default features + ink_crate_dependency.remove("default-features"); + ink_crate_dependency.remove("features"); + ink_crate_dependency.remove("optional"); + + // add ink dependencies copied from contract manifest + deps.insert("ink".into(), ink_crate_dependency.into()); + + // add features from contract + let features = cargo_toml + .entry("features") + .or_insert(Value::Table(Default::default())) + .as_table_mut() + .ok_or_else(|| anyhow::anyhow!("features should be a table"))?; + + for (feature, _) in contract_features { + if feature != "default" && feature != "std" { + features.insert( + feature.to_string(), + Value::Array(vec![format!("contract/{feature}").into()]), + ); + } + } + + let cargo_toml = toml::to_string(&cargo_toml)?; + fs::write(dir.join("Cargo.toml"), cargo_toml)?; + fs::write(dir.join("main.rs"), main_rs.to_string())?; + Ok(()) + } + + /// Generate the `main.rs` file to be executed to generate the metadata. + fn generate_main(&self) -> proc_macro2::TokenStream { + let ink_event_metadata_fns = self + .ink_event_metadata_externs + .iter() + .map(|event_metadata_fn| quote::format_ident!("{}", event_metadata_fn)) + .collect::>(); + + quote::quote!( + extern crate contract; + + extern "Rust" { + // Note: The ink! metadata codegen generates an implementation for this function, + // which is what we end up linking to here. + fn __ink_generate_metadata( + events: ::ink::prelude::vec::Vec<::ink::metadata::EventSpec> + ) -> ::ink::metadata::InkProject; + + // All `#[ink::event_definition]`s export a unique function to fetch their + // respective metadata, which we link to here. + #( fn #ink_event_metadata_fns () -> ::ink::metadata::EventSpec; )* + } + + fn main() -> Result<(), std::io::Error> { + let metadata = unsafe { + __ink_generate_metadata( + ::ink::prelude::vec![ + #( + #ink_event_metadata_fns () + ),* + ] + ) + }; + + let contents = serde_json::to_string_pretty(&metadata)?; + print!("{contents}"); + Ok(()) + } + ) + } } diff --git a/crates/build/src/workspace/mod.rs b/crates/build/src/workspace/mod.rs index a51847fd8..d5e86e04b 100644 --- a/crates/build/src/workspace/mod.rs +++ b/crates/build/src/workspace/mod.rs @@ -151,9 +151,10 @@ impl Workspace { pub(super) fn with_metadata_gen_package( &mut self, package_path: PathBuf, + ink_event_metadata_externs: Vec, ) -> Result<&mut Self> { self.with_contract_manifest(&package_path, |manifest| { - manifest.with_metadata_package()?; + manifest.with_metadata_package(ink_event_metadata_externs)?; Ok(()) }) } diff --git a/crates/build/templates/generate-metadata/main.rs b/crates/build/templates/generate-metadata/main.rs index c26034861..e69de29bb 100644 --- a/crates/build/templates/generate-metadata/main.rs +++ b/crates/build/templates/generate-metadata/main.rs @@ -1,14 +0,0 @@ -extern crate contract; - -extern "Rust" { - // Note: The ink! metdata codegen generates an implementation for this function, - // which is what we end up linking to here. - fn __ink_generate_metadata() -> ink::metadata::InkProject; -} - -fn main() -> Result<(), std::io::Error> { - let metadata = unsafe { __ink_generate_metadata() }; - let contents = serde_json::to_string_pretty(&metadata)?; - print!("{contents}"); - Ok(()) -}