1use std::{
18 fs,
19 path::{
20 Path,
21 PathBuf,
22 },
23};
24
25use anyhow::Result;
26use colored::Colorize;
27use contract_metadata::{
28 Compiler,
29 Contract,
30 ContractMetadata,
31 Language,
32 Source,
33 SourceCompiler,
34 SourceContractBinary,
35 SourceLanguage,
36 User,
37};
38use ink_metadata::InkProject;
39use semver::Version;
40use serde::{
41 Deserialize,
42 Serialize,
43};
44use url::Url;
45
46use crate::{
47 BuildMode,
48 Features,
49 Lto,
50 Network,
51 Profile,
52 UnstableFlags,
53 Verbosity,
54 code_hash,
55 crate_metadata::CrateMetadata,
56 solidity_metadata::{
57 self,
58 SolidityContractMetadata,
59 SolidityMetadataArtifacts,
60 },
61 util,
62 verbose_eprintln,
63 workspace::{
64 ManifestPath,
65 Workspace,
66 },
67};
68
69#[derive(serde::Serialize, serde::Deserialize)]
71pub enum MetadataArtifacts {
72 Ink(InkMetadataArtifacts),
74 Solidity(SolidityMetadataArtifacts),
76}
77
78impl MetadataArtifacts {
79 pub(crate) fn exists(&self) -> bool {
81 match self {
82 MetadataArtifacts::Ink(ink_metadata_artifacts) => {
83 ink_metadata_artifacts.dest_metadata.exists()
84 && ink_metadata_artifacts.dest_bundle.exists()
85 }
86 MetadataArtifacts::Solidity(solidity_metadata_artifacts) => {
87 solidity_metadata_artifacts.dest_abi.exists()
88 && solidity_metadata_artifacts.dest_metadata.exists()
89 }
90 }
91 }
92}
93
94#[derive(serde::Serialize, serde::Deserialize)]
96pub struct InkMetadataArtifacts {
97 pub dest_metadata: PathBuf,
99 pub dest_bundle: PathBuf,
101}
102
103struct ExtendedMetadataResult {
105 source: Source,
106 contract: Contract,
107 user: Option<User>,
108}
109
110#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
115pub struct BuildInfo {
116 pub rust_toolchain: String,
118 pub cargo_contract_version: Version,
120 pub build_mode: BuildMode,
122}
123
124impl TryFrom<BuildInfo> for serde_json::Map<String, serde_json::Value> {
125 type Error = serde_json::Error;
126
127 fn try_from(build_info: BuildInfo) -> Result<Self, Self::Error> {
128 let tmp = serde_json::to_string(&build_info)?;
129 serde_json::from_str(&tmp)
130 }
131}
132
133#[derive(Debug, Serialize, Deserialize)]
135pub struct CodegenMetadata {
136 ink: Option<InkProject>,
137 solidity: Option<ink_metadata::sol::ContractMetadata>,
138}
139
140#[allow(clippy::too_many_arguments)]
144pub fn execute(
145 crate_metadata: &CrateMetadata,
146 final_contract_binary: &Path,
147 metadata_artifacts: &MetadataArtifacts,
148 features: &Features,
149 network: Network,
150 verbosity: Verbosity,
151 unstable_options: &UnstableFlags,
152 build_info: BuildInfo,
153) -> Result<()> {
154 let ExtendedMetadataResult {
156 source,
157 contract,
158 user,
159 } = extended_metadata(crate_metadata, final_contract_binary, build_info)?;
160
161 let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
162 verbose_eprintln!(
163 verbosity,
164 " {} {}",
165 "[==]".bold(),
166 "Generating metadata".bright_green().bold(),
167 );
168 let target_dir = crate_metadata
169 .target_directory
170 .to_string_lossy()
171 .to_string();
172 let mut args = vec![
173 "--package".to_owned(),
174 "metadata-gen".to_owned(),
175 manifest_path.cargo_arg()?,
176 "--target-dir".to_owned(),
177 target_dir,
178 "--release".to_owned(),
179 ];
180 network.append_to_args(&mut args);
181 features.append_to_args(&mut args);
182
183 #[cfg(windows)]
184 let link_dead_code = "";
185
186 #[cfg(not(windows))]
187 let link_dead_code = "\x1f-Clink-dead-code";
188
189 let mut abi_cfg = String::new();
190 if let Some(abi) = crate_metadata.abi {
191 abi_cfg.push('\x1f');
192 abi_cfg.push_str(&abi.cargo_encoded_rustflag());
193 }
194
195 let cmd = util::cargo_cmd(
196 "run",
197 args,
198 crate_metadata.manifest_path.directory(),
199 verbosity,
200 vec![(
201 "CARGO_ENCODED_RUSTFLAGS",
202 Some(format!("--cap-lints=allow{link_dead_code}{abi_cfg}")),
203 )],
204 );
205 let output = cmd.stdout_capture().run()?;
206 let codegen_meta: CodegenMetadata = serde_json::from_slice(&output.stdout)?;
207 match metadata_artifacts {
208 MetadataArtifacts::Ink(ink_metadata_artifacts) => {
209 let ink_project = codegen_meta
210 .ink
211 .ok_or_else(|| anyhow::anyhow!("Expected ink! metadata"))?;
212 let ink_meta = match serde_json::to_value(&ink_project)? {
213 serde_json::Value::Object(meta) => meta,
214 _ => anyhow::bail!("Expected ink! metadata object"),
215 };
216 let metadata =
217 ContractMetadata::new(source, contract, None, user, ink_meta);
218
219 write_metadata(ink_metadata_artifacts, metadata, &verbosity, false)?;
220 }
221 MetadataArtifacts::Solidity(solidity_metadata_artifacts) => {
222 let sol_meta = codegen_meta.solidity.ok_or_else(|| {
223 anyhow::anyhow!("Expected Solidity compatibility metadata")
224 })?;
225 let sol_abi = solidity_metadata::generate_abi(&sol_meta)?;
226 let metadata = solidity_metadata::generate_metadata(
227 &sol_meta,
228 sol_abi,
229 source,
230 contract,
231 crate_metadata,
232 None,
233 )?;
234
235 write_solidity_metadata(
236 solidity_metadata_artifacts,
237 metadata,
238 &verbosity,
239 false,
240 )?;
241 }
242 }
243
244 Ok(())
245 };
246
247 if unstable_options.original_manifest {
248 generate_metadata(&crate_metadata.manifest_path)?;
249 } else {
250 Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
251 .with_root_package_manifest(|manifest| {
252 manifest
253 .with_added_crate_type("rlib")?
254 .with_profile_release_defaults(Profile {
255 lto: Some(Lto::Thin),
256 ..Profile::default()
257 })?
258 .with_merged_workspace_dependencies(crate_metadata)?
259 .with_empty_workspace();
260 Ok(())
261 })?
262 .with_metadata_gen_package()?
263 .using_temp(generate_metadata)?;
264 }
265
266 Ok(())
267}
268
269pub fn write_metadata(
270 metadata_artifacts: &InkMetadataArtifacts,
271 metadata: ContractMetadata,
272 verbosity: &Verbosity,
273 overwrite: bool,
274) -> Result<()> {
275 {
276 let mut metadata = metadata.clone();
277 metadata.remove_source_contract_binary_attribute();
278 let contents = serde_json::to_string_pretty(&metadata)?;
279 fs::write(&metadata_artifacts.dest_metadata, contents)?;
280 }
281
282 if overwrite {
283 verbose_eprintln!(
284 verbosity,
285 " {} {}",
286 "[==]".bold(),
287 "Updating paths".bright_cyan().bold()
288 );
289 } else {
290 verbose_eprintln!(
291 verbosity,
292 " {} {}",
293 "[==]".bold(),
294 "Generating bundle".bright_green().bold()
295 );
296 }
297 let contents = serde_json::to_string(&metadata)?;
298 fs::write(&metadata_artifacts.dest_bundle, contents)?;
299
300 Ok(())
301}
302
303pub fn write_solidity_metadata(
305 metadata_artifacts: &SolidityMetadataArtifacts,
306 metadata: SolidityContractMetadata,
307 verbosity: &Verbosity,
308 overwrite: bool,
309) -> Result<()> {
310 if overwrite {
311 verbose_eprintln!(
312 verbosity,
313 " {} {}",
314 "[==]".bold(),
315 "Updating Solidity compatible metadata".bright_cyan().bold()
316 );
317 } else {
318 verbose_eprintln!(
319 verbosity,
320 " {} {}",
321 "[==]".bold(),
322 "Generating Solidity compatible metadata"
323 .bright_green()
324 .bold()
325 );
326 }
327
328 solidity_metadata::write_abi(&metadata.output.abi, &metadata_artifacts.dest_abi)?;
330
331 solidity_metadata::write_metadata(&metadata, &metadata_artifacts.dest_metadata)?;
333
334 Ok(())
335}
336
337fn extended_metadata(
339 crate_metadata: &CrateMetadata,
340 final_contract_binary: &Path,
341 build_info: BuildInfo,
342) -> Result<ExtendedMetadataResult> {
343 let contract_package = &crate_metadata.root_package;
344 let ink_version = &crate_metadata.ink_version;
345 let rust_version = Version::parse(&rustc_version::version()?.to_string())?;
346 let contract_name = contract_package.name.clone();
347 let contract_version = Version::parse(&contract_package.version.to_string())?;
348 let contract_authors = contract_package.authors.clone();
349 let description = contract_package.description.clone();
351 let documentation = crate_metadata.documentation.clone();
352 let repository = contract_package
353 .repository
354 .as_ref()
355 .map(|repo| Url::parse(repo))
356 .transpose()?;
357 let homepage = crate_metadata.homepage.clone();
358 let license = contract_package.license.clone();
359 let source = {
360 let lang = SourceLanguage::new(Language::Ink, ink_version.clone());
361 let compiler = SourceCompiler::new(Compiler::RustC, rust_version);
362 let contract_binary = fs::read(final_contract_binary)?;
363 let hash = code_hash(contract_binary.as_slice());
364 Source::new(
365 Some(SourceContractBinary::new(contract_binary)),
366 hash.into(),
367 lang,
368 compiler,
369 Some(build_info.try_into()?),
370 )
371 };
372
373 let mut builder = Contract::builder();
375 builder
376 .name(contract_name)
377 .version(contract_version)
378 .authors(contract_authors);
379
380 if let Some(description) = description {
381 builder.description(description);
382 }
383
384 if let Some(documentation) = documentation {
385 builder.documentation(documentation);
386 }
387
388 if let Some(repository) = repository {
389 builder.repository(repository);
390 }
391
392 if let Some(homepage) = homepage {
393 builder.homepage(homepage);
394 }
395
396 if let Some(license) = license {
397 builder.license(license);
398 }
399
400 let contract = builder.build().map_err(|err| {
401 anyhow::anyhow!("Invalid contract metadata builder state: {err}")
402 })?;
403
404 let user = crate_metadata.user.clone().map(User::new);
406
407 Ok(ExtendedMetadataResult {
408 source,
409 contract,
410 user,
411 })
412}