contract_build/
metadata.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{
18    code_hash,
19    crate_metadata::CrateMetadata,
20    util,
21    verbose_eprintln,
22    workspace::{
23        ManifestPath,
24        Workspace,
25    },
26    BuildMode,
27    Features,
28    Lto,
29    Network,
30    OptimizationPasses,
31    Profile,
32    UnstableFlags,
33    Verbosity,
34};
35
36use anyhow::Result;
37use colored::Colorize;
38use contract_metadata::{
39    Compiler,
40    Contract,
41    ContractMetadata,
42    Language,
43    Source,
44    SourceCompiler,
45    SourceLanguage,
46    SourceWasm,
47    User,
48};
49use semver::Version;
50use std::{
51    fs,
52    path::{
53        Path,
54        PathBuf,
55    },
56};
57use url::Url;
58
59/// Artifacts resulting from metadata generation.
60#[derive(serde::Serialize, serde::Deserialize)]
61pub struct MetadataArtifacts {
62    /// Path to the resulting metadata file.
63    pub dest_metadata: PathBuf,
64    /// Path to the bundled file.
65    pub dest_bundle: PathBuf,
66}
67
68/// Result of generating the extended contract project metadata
69struct ExtendedMetadataResult {
70    source: Source,
71    contract: Contract,
72    user: Option<User>,
73}
74
75/// Information about the settings used to build a particular ink! contract.
76///
77/// Note that this should be an optional part of the metadata since it may not necessarily
78/// translate to other languages (e.g ask!, Solidity).
79#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
80pub struct BuildInfo {
81    /// The Rust toolchain used to build the contract.
82    pub rust_toolchain: String,
83    /// The version of `cargo-contract` used to build the contract.
84    pub cargo_contract_version: Version,
85    /// The type of build that was used when building the contract.
86    pub build_mode: BuildMode,
87    /// Information about the `wasm-opt` optimization settings.
88    pub wasm_opt_settings: WasmOptSettings,
89}
90
91impl TryFrom<BuildInfo> for serde_json::Map<String, serde_json::Value> {
92    type Error = serde_json::Error;
93
94    fn try_from(build_info: BuildInfo) -> Result<Self, Self::Error> {
95        let tmp = serde_json::to_string(&build_info)?;
96        serde_json::from_str(&tmp)
97    }
98}
99
100/// Settings used when optimizing the Wasm binary using Binaryen's `wasm-opt`.
101#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
102pub struct WasmOptSettings {
103    /// The level of optimization used during the `wasm-opt` run.
104    pub optimization_passes: OptimizationPasses,
105    /// Whether or not the Wasm name section should be kept.
106    pub keep_debug_symbols: bool,
107}
108
109/// Generates a file with metadata describing the ABI of the smart contract.
110///
111/// It does so by generating and invoking a temporary workspace member.
112#[allow(clippy::too_many_arguments)]
113pub fn execute(
114    crate_metadata: &CrateMetadata,
115    final_contract_wasm: &Path,
116    metadata_artifacts: &MetadataArtifacts,
117    features: &Features,
118    network: Network,
119    verbosity: Verbosity,
120    unstable_options: &UnstableFlags,
121    build_info: BuildInfo,
122) -> Result<()> {
123    // build the extended contract project metadata
124    let ExtendedMetadataResult {
125        source,
126        contract,
127        user,
128    } = extended_metadata(crate_metadata, final_contract_wasm, build_info)?;
129
130    let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
131        verbose_eprintln!(
132            verbosity,
133            " {} {}",
134            "[==]".bold(),
135            "Generating metadata".bright_green().bold(),
136        );
137        let target_dir = crate_metadata
138            .target_directory
139            .to_string_lossy()
140            .to_string();
141        let mut args = vec![
142            "--package".to_owned(),
143            "metadata-gen".to_owned(),
144            manifest_path.cargo_arg()?,
145            "--target-dir".to_owned(),
146            target_dir,
147            "--release".to_owned(),
148        ];
149        network.append_to_args(&mut args);
150        features.append_to_args(&mut args);
151
152        #[cfg(windows)]
153        let link_dead_code = "";
154
155        #[cfg(not(windows))]
156        let link_dead_code = "\x1f-Clink-dead-code";
157
158        let cmd = util::cargo_cmd(
159            "run",
160            args,
161            crate_metadata.manifest_path.directory(),
162            verbosity,
163            vec![(
164                "CARGO_ENCODED_RUSTFLAGS",
165                Some(format!("--cap-lints=allow{link_dead_code}")),
166            )],
167        );
168        let output = cmd.stdout_capture().run()?;
169
170        let ink_meta: serde_json::Map<String, serde_json::Value> =
171            serde_json::from_slice(&output.stdout)?;
172        let metadata = ContractMetadata::new(source, contract, None, user, ink_meta);
173
174        write_metadata(metadata_artifacts, metadata, &verbosity, false)?;
175
176        Ok(())
177    };
178
179    if unstable_options.original_manifest {
180        generate_metadata(&crate_metadata.manifest_path)?;
181    } else {
182        Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
183            .with_root_package_manifest(|manifest| {
184                manifest
185                    .with_added_crate_type("rlib")?
186                    .with_profile_release_defaults(Profile {
187                        lto: Some(Lto::Thin),
188                        ..Profile::default()
189                    })?
190                    .with_merged_workspace_dependencies(crate_metadata)?
191                    .with_empty_workspace();
192                Ok(())
193            })?
194            .with_metadata_gen_package()?
195            .using_temp(generate_metadata)?;
196    }
197
198    Ok(())
199}
200
201pub fn write_metadata(
202    metadata_artifacts: &MetadataArtifacts,
203    metadata: ContractMetadata,
204    verbosity: &Verbosity,
205    overwrite: bool,
206) -> Result<()> {
207    {
208        let mut metadata = metadata.clone();
209        metadata.remove_source_wasm_attribute();
210        let contents = serde_json::to_string_pretty(&metadata)?;
211        fs::write(&metadata_artifacts.dest_metadata, contents)?;
212    }
213
214    if overwrite {
215        verbose_eprintln!(
216            verbosity,
217            " {} {}",
218            "[==]".bold(),
219            "Updating paths".bright_cyan().bold()
220        );
221    } else {
222        verbose_eprintln!(
223            verbosity,
224            " {} {}",
225            "[==]".bold(),
226            "Generating bundle".bright_green().bold()
227        );
228    }
229    let contents = serde_json::to_string(&metadata)?;
230    fs::write(&metadata_artifacts.dest_bundle, contents)?;
231
232    Ok(())
233}
234
235/// Generate the extended contract project metadata
236fn extended_metadata(
237    crate_metadata: &CrateMetadata,
238    final_contract_wasm: &Path,
239    build_info: BuildInfo,
240) -> Result<ExtendedMetadataResult> {
241    let contract_package = &crate_metadata.root_package;
242    let ink_version = &crate_metadata.ink_version;
243    let rust_version = Version::parse(&rustc_version::version()?.to_string())?;
244    let contract_name = contract_package.name.clone();
245    let contract_version = Version::parse(&contract_package.version.to_string())?;
246    let contract_authors = contract_package.authors.clone();
247    // optional
248    let description = contract_package.description.clone();
249    let documentation = crate_metadata.documentation.clone();
250    let repository = contract_package
251        .repository
252        .as_ref()
253        .map(|repo| Url::parse(repo))
254        .transpose()?;
255    let homepage = crate_metadata.homepage.clone();
256    let license = contract_package.license.clone();
257    let source = {
258        let lang = SourceLanguage::new(Language::Ink, ink_version.clone());
259        let compiler = SourceCompiler::new(Compiler::RustC, rust_version);
260        let wasm = fs::read(final_contract_wasm)?;
261        let hash = code_hash(wasm.as_slice());
262        Source::new(
263            Some(SourceWasm::new(wasm)),
264            hash.into(),
265            lang,
266            compiler,
267            Some(build_info.try_into()?),
268        )
269    };
270
271    // Required contract fields
272    let mut builder = Contract::builder();
273    builder
274        .name(contract_name)
275        .version(contract_version)
276        .authors(contract_authors);
277
278    if let Some(description) = description {
279        builder.description(description);
280    }
281
282    if let Some(documentation) = documentation {
283        builder.documentation(documentation);
284    }
285
286    if let Some(repository) = repository {
287        builder.repository(repository);
288    }
289
290    if let Some(homepage) = homepage {
291        builder.homepage(homepage);
292    }
293
294    if let Some(license) = license {
295        builder.license(license);
296    }
297
298    let contract = builder.build().map_err(|err| {
299        anyhow::anyhow!("Invalid contract metadata builder state: {}", err)
300    })?;
301
302    // user defined metadata
303    let user = crate_metadata.user.clone().map(User::new);
304
305    Ok(ExtendedMetadataResult {
306        source,
307        contract,
308        user,
309    })
310}