1#![doc = include_str!("../README.md")]
18#![deny(unused_crate_dependencies)]
19
20use contract_metadata::{
21 compatibility::check_contract_ink_compatibility,
22 ContractMetadata,
23};
24use which as _;
25
26mod args;
27mod crate_metadata;
28mod docker;
29pub mod metadata;
30mod new;
31mod post_process_wasm;
32#[cfg(test)]
33mod tests;
34pub mod util;
35mod validate_wasm;
36mod wasm_opt;
37mod workspace;
38
39#[deprecated(since = "2.0.2", note = "Use MetadataArtifacts instead")]
40pub use self::metadata::MetadataArtifacts as MetadataResult;
41
42pub use self::{
43 args::{
44 BuildArtifacts,
45 BuildMode,
46 Features,
47 Network,
48 OutputType,
49 Target,
50 UnstableFlags,
51 UnstableOptions,
52 Verbosity,
53 VerbosityFlags,
54 },
55 crate_metadata::CrateMetadata,
56 metadata::{
57 BuildInfo,
58 MetadataArtifacts,
59 WasmOptSettings,
60 },
61 new::new_contract_project,
62 post_process_wasm::{
63 load_module,
64 post_process_wasm,
65 },
66 util::DEFAULT_KEY_COL_WIDTH,
67 wasm_opt::{
68 OptimizationPasses,
69 OptimizationResult,
70 },
71 workspace::{
72 Lto,
73 Manifest,
74 ManifestPath,
75 OptLevel,
76 PanicStrategy,
77 Profile,
78 Workspace,
79 },
80};
81
82use crate::wasm_opt::WasmOptHandler;
83pub use docker::{
84 docker_build,
85 ImageVariant,
86};
87
88use anyhow::{
89 Context,
90 Result,
91};
92use colored::Colorize;
93use semver::Version;
94use std::{
95 fs,
96 path::{
97 Path,
98 PathBuf,
99 },
100 process::Command,
101 str,
102};
103use strum::IntoEnumIterator;
104
105pub const DEFAULT_MAX_MEMORY_PAGES: u64 = 16;
107
108const VERSION: &str = env!("CARGO_PKG_VERSION");
110
111pub(crate) mod linting {
115 pub const TOOLCHAIN_VERSION: &str = "nightly-2024-09-05";
118 pub const GIT_URL: &str = "https://github.com/use-ink/ink/";
120 pub const GIT_REV: &str = "5ec034ca05e1239371e1d1c904d7580b375da9ca";
122}
123
124#[derive(Clone)]
126pub struct ExecuteArgs {
127 pub manifest_path: ManifestPath,
129 pub verbosity: Verbosity,
130 pub build_mode: BuildMode,
131 pub features: Features,
132 pub network: Network,
133 pub build_artifact: BuildArtifacts,
134 pub unstable_flags: UnstableFlags,
135 pub optimization_passes: Option<OptimizationPasses>,
136 pub keep_debug_symbols: bool,
137 pub extra_lints: bool,
138 pub output_type: OutputType,
139 pub skip_wasm_validation: bool,
140 pub target: Target,
141 pub max_memory_pages: u64,
142 pub image: ImageVariant,
143}
144
145impl Default for ExecuteArgs {
146 fn default() -> Self {
147 Self {
148 manifest_path: Default::default(),
149 verbosity: Default::default(),
150 build_mode: Default::default(),
151 features: Default::default(),
152 network: Default::default(),
153 build_artifact: Default::default(),
154 unstable_flags: Default::default(),
155 optimization_passes: Default::default(),
156 keep_debug_symbols: Default::default(),
157 extra_lints: Default::default(),
158 output_type: Default::default(),
159 skip_wasm_validation: Default::default(),
160 target: Default::default(),
161 max_memory_pages: DEFAULT_MAX_MEMORY_PAGES,
162 image: Default::default(),
163 }
164 }
165}
166
167#[derive(serde::Serialize, serde::Deserialize)]
169pub struct BuildResult {
170 pub dest_wasm: Option<PathBuf>,
172 pub metadata_result: Option<MetadataArtifacts>,
174 pub target_directory: PathBuf,
176 pub optimization_result: Option<OptimizationResult>,
178 pub build_mode: BuildMode,
180 pub build_artifact: BuildArtifacts,
182 pub verbosity: Verbosity,
184 pub image: Option<String>,
186 #[serde(skip_serializing, skip_deserializing)]
188 pub output_type: OutputType,
189}
190
191impl BuildResult {
192 pub fn display(&self) -> String {
193 let opt_size_diff = if let Some(ref opt_result) = self.optimization_result {
194 let size_diff = format!(
195 "\nOriginal wasm size: {}, Optimized: {}\n\n",
196 format!("{:.1}K", opt_result.original_size).bold(),
197 format!("{:.1}K", opt_result.optimized_size).bold(),
198 );
199 debug_assert!(
200 opt_result.optimized_size > 0.0,
201 "optimized file size must be greater 0"
202 );
203 size_diff
204 } else {
205 "\n".to_string()
206 };
207
208 let build_mode = format!(
209 "The contract was built in {} mode.\n\n",
210 format!("{}", self.build_mode).to_uppercase().bold(),
211 );
212
213 if self.build_artifact == BuildArtifacts::CodeOnly {
214 let out = format!(
215 "{}{}Your contract's code is ready. You can find it here:\n{}",
216 opt_size_diff,
217 build_mode,
218 self.dest_wasm
219 .as_ref()
220 .expect("wasm path must exist")
221 .display()
222 .to_string()
223 .bold()
224 );
225 return out
226 };
227
228 let mut out = format!(
229 "{}{}Your contract artifacts are ready. You can find them in:\n{}\n\n",
230 opt_size_diff,
231 build_mode,
232 self.target_directory.display().to_string().bold(),
233 );
234 if let Some(metadata_result) = self.metadata_result.as_ref() {
235 let bundle = format!(
236 " - {} (code + metadata)\n",
237 util::base_name(&metadata_result.dest_bundle).bold()
238 );
239 out.push_str(&bundle);
240 }
241 if let Some(dest_wasm) = self.dest_wasm.as_ref() {
242 let wasm = format!(
243 " - {} (the contract's code)\n",
244 util::base_name(dest_wasm).bold()
245 );
246 out.push_str(&wasm);
247 }
248 if let Some(metadata_result) = self.metadata_result.as_ref() {
249 let metadata = format!(
250 " - {} (the contract's metadata)",
251 util::base_name(&metadata_result.dest_metadata).bold()
252 );
253 out.push_str(&metadata);
254 }
255 out
256 }
257
258 pub fn serialize_json(&self) -> Result<String> {
260 Ok(serde_json::to_string_pretty(self)?)
261 }
262}
263
264#[allow(clippy::too_many_arguments)]
286fn exec_cargo_for_onchain_target(
287 crate_metadata: &CrateMetadata,
288 command: &str,
289 features: &Features,
290 build_mode: &BuildMode,
291 network: &Network,
292 verbosity: &Verbosity,
293 unstable_flags: &UnstableFlags,
294 target: &Target,
295) -> Result<()> {
296 let cargo_build = |manifest_path: &ManifestPath| {
297 let target_dir = format!(
298 "--target-dir={}",
299 crate_metadata.target_directory.to_string_lossy()
300 );
301 let mut args = vec![target_dir, "--release".to_owned()];
302 args.extend(onchain_cargo_options(target));
303 network.append_to_args(&mut args);
304
305 let mut features = features.clone();
306 if build_mode == &BuildMode::Debug {
307 features.push("ink/ink-debug");
308 } else {
309 args.push("-Zbuild-std-features=panic_immediate_abort".to_owned());
310 }
311 features.append_to_args(&mut args);
312 let mut env = Vec::new();
313 if rustc_version::version_meta()?.channel == rustc_version::Channel::Stable {
314 env.push(("RUSTC_BOOTSTRAP", Some("1".to_string())))
316 }
317
318 let rustflags = {
326 let common_flags = "-Clinker-plugin-lto";
327 if let Some(target_flags) = target.rustflags() {
328 format!("{}\x1f{}", common_flags, target_flags)
329 } else {
330 common_flags.to_string()
331 }
332 };
333
334 if matches!(target, Target::RiscV) {
336 fs::create_dir_all(&crate_metadata.target_directory)?;
337 let path = crate_metadata
338 .target_directory
339 .join(".riscv_memory_layout.ld");
340 fs::write(&path, include_bytes!("../riscv_memory_layout.ld"))?;
341 let path = path.display();
342 env.push((
343 "CARGO_ENCODED_RUSTFLAGS",
344 Some(format!("{rustflags}\x1f-Clink-arg=-T{path}",)),
345 ));
346 } else {
347 env.push(("CARGO_ENCODED_RUSTFLAGS", Some(rustflags)));
348 };
349
350 execute_cargo(util::cargo_cmd(
351 command,
352 &args,
353 manifest_path.directory(),
354 *verbosity,
355 env,
356 ))
357 };
358
359 if unstable_flags.original_manifest {
360 verbose_eprintln!(
361 verbosity,
362 "{} {}",
363 "warning:".yellow().bold(),
364 "with 'original-manifest' enabled, the contract binary may not be of optimal size."
365 .bold()
366 );
367 cargo_build(&crate_metadata.manifest_path)?;
368 } else {
369 Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
370 .with_root_package_manifest(|manifest| {
371 manifest
372 .with_replaced_lib_to_bin()?
373 .with_profile_release_defaults(Profile::default_contract_release())?
374 .with_merged_workspace_dependencies(crate_metadata)?
375 .with_empty_workspace();
376 Ok(())
377 })?
378 .using_temp(cargo_build)?;
379 }
380
381 Ok(())
382}
383
384fn check_buffer_size_invoke_cargo_clean(
388 crate_metadata: &CrateMetadata,
389 verbosity: &Verbosity,
390) -> Result<()> {
391 if let Ok(buffer_size) = std::env::var("INK_STATIC_BUFFER_SIZE") {
392 let buffer_size_value: u64 = buffer_size
393 .parse()
394 .context("`INK_STATIC_BUFFER_SIZE` must have an integer value.")?;
395
396 let extract_buffer_size = |metadata_path: PathBuf| -> Result<u64> {
397 let size = ContractMetadata::load(metadata_path)
398 .context("Metadata is not present")?
399 .abi
400 .get("spec")
402 .context("spec field should be present in ABI.")?
403 .get("environment")
405 .context("environment field should be present in ABI.")?
406 .get("staticBufferSize")
408 .context("`staticBufferSize` must be specified.")?
409 .as_u64()
411 .context("`staticBufferSize` value must be an integer.")?;
412
413 Ok(size)
414 };
415
416 let cargo = util::cargo_cmd(
417 "clean",
418 Vec::<&str>::new(),
419 crate_metadata.manifest_path.directory(),
420 *verbosity,
421 vec![],
422 );
423
424 match extract_buffer_size(crate_metadata.metadata_path()) {
425 Ok(contract_buffer_size) if contract_buffer_size == buffer_size_value => {
426 verbose_eprintln!(
427 verbosity,
428 "{} {}",
429 "info:".green().bold(),
430 "Detected a configured buffer size, but the value is already specified."
431 .bold()
432 );
433 }
434 Ok(_) => {
435 verbose_eprintln!(
436 verbosity,
437 "{} {}",
438 "warning:".yellow().bold(),
439 "Detected a change in the configured buffer size. Rebuilding the project."
440 .bold()
441 );
442 execute_cargo(cargo)?;
443 }
444 Err(_) => {
445 verbose_eprintln!(
446 verbosity,
447 "{} {}",
448 "warning:".yellow().bold(),
449 "Cannot find the previous size of the static buffer. Rebuilding the project."
450 .bold()
451 );
452 execute_cargo(cargo)?;
453 }
454 }
455 }
456 Ok(())
457}
458
459fn execute_cargo(cargo: duct::Expression) -> Result<()> {
462 match cargo.unchecked().run() {
463 Ok(out) if out.status.success() => Ok(()),
464 Ok(out) => anyhow::bail!(String::from_utf8_lossy(&out.stderr).to_string()),
465 Err(e) => anyhow::bail!("Cannot run `cargo` command: {:?}", e),
466 }
467}
468
469fn lint(
472 extra_lints: bool,
473 crate_metadata: &CrateMetadata,
474 target: &Target,
475 verbosity: &Verbosity,
476) -> Result<()> {
477 verbose_eprintln!(
478 verbosity,
479 " {} {}",
480 "[==]".bold(),
481 "Checking clippy linting rules".bright_green().bold()
482 );
483 exec_cargo_clippy(crate_metadata, *verbosity)?;
484
485 if extra_lints || matches!(target, Target::RiscV) {
489 verbose_eprintln!(
490 verbosity,
491 " {} {}",
492 "[==]".bold(),
493 "Checking ink! linting rules".bright_green().bold()
494 );
495 exec_cargo_dylint(extra_lints, crate_metadata, target, *verbosity)?;
496 }
497
498 Ok(())
499}
500
501fn exec_cargo_clippy(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Result<()> {
503 let args = [
504 "--all-features",
505 "--",
507 "-Dclippy::arithmetic_side_effects",
511 "-Dclippy::cast_possible_truncation",
513 "-Dclippy::cast_possible_wrap",
514 "-Dclippy::cast_sign_loss",
515 ];
516 execute_cargo(util::cargo_cmd(
518 "clippy",
519 args,
520 crate_metadata.manifest_path.directory(),
521 verbosity,
522 vec![],
523 ))
524}
525
526fn onchain_cargo_options(target: &Target) -> Vec<String> {
528 vec![
529 format!("--target={}", target.llvm_target()),
530 "-Zbuild-std=core,alloc".to_owned(),
531 "--no-default-features".to_owned(),
532 ]
533}
534
535fn exec_cargo_dylint(
540 extra_lints: bool,
541 crate_metadata: &CrateMetadata,
542 target: &Target,
543 verbosity: Verbosity,
544) -> Result<()> {
545 check_dylint_requirements(crate_metadata.manifest_path.directory())?;
546
547 let verbosity = match verbosity {
549 Verbosity::Verbose => Verbosity::Default,
550 Verbosity::Default | Verbosity::Quiet => Verbosity::Quiet,
551 };
552
553 let mut args = if extra_lints {
554 vec![
555 "--lib=ink_linting_mandatory".to_owned(),
556 "--lib=ink_linting".to_owned(),
557 ]
558 } else {
559 vec!["--lib=ink_linting_mandatory".to_owned()]
560 };
561 args.push("--".to_owned());
562 args.extend(onchain_cargo_options(target));
565
566 let target_dir = &crate_metadata.target_directory.to_string_lossy();
567 let env = vec![
568 ("CARGO_TARGET_DIR", Some(target_dir.to_string())),
575 ("RUSTC_WRAPPER", None),
580 ];
581
582 Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
583 .with_root_package_manifest(|manifest| {
584 manifest.with_dylint()?;
585 Ok(())
586 })?
587 .using_temp(|manifest_path| {
588 let cargo = util::cargo_cmd(
589 "dylint",
590 &args,
591 manifest_path.directory(),
592 verbosity,
593 env,
594 );
595 cargo.run()?;
596 Ok(())
597 })?;
598
599 Ok(())
600}
601
602fn check_dylint_requirements(_working_dir: Option<&Path>) -> Result<()> {
610 let execute_cmd = |cmd: &mut Command| {
611 let mut child = if let Ok(child) = cmd
612 .stdout(std::process::Stdio::null())
613 .stderr(std::process::Stdio::null())
614 .spawn()
615 {
616 child
617 } else {
618 tracing::debug!("Error spawning `{:?}`", cmd);
619 return false
620 };
621
622 child.wait().map(|ret| ret.success()).unwrap_or_else(|err| {
623 tracing::debug!("Error waiting for `{:?}`: {:?}", cmd, err);
624 false
625 })
626 };
627
628 if let Ok(output) = Command::new("rustup").arg("toolchain").arg("list").output() {
630 anyhow::ensure!(
631 String::from_utf8_lossy(&output.stdout).contains(linting::TOOLCHAIN_VERSION),
632 format!(
633 "Toolchain `{0}` was not found!\n\
634 This specific version is required to provide additional source code analysis.\n\n\
635 You can install it by executing:\n\
636 rustup install {0}\n\
637 rustup component add rust-src --toolchain {0}\n\
638 rustup run {0} cargo install cargo-dylint dylint-link",
639 linting::TOOLCHAIN_VERSION,
640 )
641 .to_string()
642 .bright_yellow());
643 } else {
644 anyhow::bail!(format!(
645 "Toolchain `{0}` was not found!\n\
646 This specific version is required to provide additional source code analysis.\n\n\
647 Install `rustup` according to https://rustup.rs/ and then run:\
648 rustup install {0}\n\
649 rustup component add rust-src --toolchain {0}\n\
650 rustup run {0} cargo install cargo-dylint dylint-link",
651 linting::TOOLCHAIN_VERSION,
652 )
653 .to_string()
654 .bright_yellow());
655 }
656
657 #[cfg(not(test))]
660 let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
661 #[cfg(test)]
662 let cargo = "cargo";
663
664 if !execute_cmd(Command::new(cargo).arg("dylint").arg("--version")) {
665 anyhow::bail!("cargo-dylint was not found!\n\
666 Make sure it is installed and the binary is in your PATH environment.\n\n\
667 You can install it by executing `cargo install cargo-dylint`."
668 .to_string()
669 .bright_yellow());
670 }
671
672 #[cfg(windows)]
676 let dylint_link_found = which::which("dylint-link").is_ok();
677 #[cfg(not(windows))]
678 let dylint_link_found = execute_cmd(Command::new("dylint-link").arg("--version"));
679 if !dylint_link_found {
680 anyhow::bail!("dylint-link was not found!\n\
681 Make sure it is installed and the binary is in your PATH environment.\n\n\
682 You can install it by executing `cargo install dylint-link`."
683 .to_string()
684 .bright_yellow());
685 }
686
687 Ok(())
688}
689
690pub fn assert_debug_mode_supported(ink_version: &Version) -> Result<()> {
694 tracing::debug!("Contract version: {:?}", ink_version);
695 let minimum_version = Version::parse("3.0.0-rc4").expect("parsing version failed");
696 if ink_version < &minimum_version {
697 anyhow::bail!(
698 "Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
699 );
700 }
701 Ok(())
702}
703
704pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
709 let ExecuteArgs {
710 manifest_path,
711 verbosity,
712 features,
713 build_mode,
714 network,
715 build_artifact,
716 unstable_flags,
717 optimization_passes,
718 extra_lints,
719 output_type,
720 target,
721 ..
722 } = &args;
723
724 if build_mode == &BuildMode::Verifiable {
726 return docker_build(args)
727 }
728
729 let optimization_passes = match optimization_passes {
732 Some(opt_passes) => *opt_passes,
733 None => {
734 let mut manifest = Manifest::new(manifest_path.clone())?;
735 manifest.profile_optimization_passes().unwrap_or_default()
738 }
739 };
740
741 let crate_metadata = CrateMetadata::collect(manifest_path, *target)?;
742
743 if build_mode == &BuildMode::Debug {
744 assert_debug_mode_supported(&crate_metadata.ink_version)?;
745 }
746
747 if let Err(e) = check_contract_ink_compatibility(&crate_metadata.ink_version, None) {
748 eprintln!("{} {}", "warning:".yellow().bold(), e.to_string().bold());
749 }
750
751 let clean_metadata = || {
752 fs::remove_file(crate_metadata.metadata_path()).ok();
753 fs::remove_file(crate_metadata.contract_bundle_path()).ok();
754 };
755
756 let (opt_result, metadata_result, dest_wasm) = match build_artifact {
757 BuildArtifacts::CheckOnly => {
758 lint(*extra_lints, &crate_metadata, target, verbosity)?;
760 (None, None, None)
761 }
762 BuildArtifacts::CodeOnly => {
763 clean_metadata();
765 let (opt_result, _, dest_wasm) =
766 local_build(&crate_metadata, &optimization_passes, &args)?;
767 (opt_result, None, Some(dest_wasm))
768 }
769 BuildArtifacts::All => {
770 let (opt_result, build_info, dest_wasm) =
771 local_build(&crate_metadata, &optimization_passes, &args).inspect_err(
772 |_| {
773 clean_metadata();
775 },
776 )?;
777
778 let metadata_result = MetadataArtifacts {
779 dest_metadata: crate_metadata.metadata_path(),
780 dest_bundle: crate_metadata.contract_bundle_path(),
781 };
782
783 if opt_result.is_some()
786 || !metadata_result.dest_metadata.exists()
787 || !metadata_result.dest_bundle.exists()
788 {
789 clean_metadata();
791 metadata::execute(
792 &crate_metadata,
793 dest_wasm.as_path(),
794 &metadata_result,
795 features,
796 *network,
797 *verbosity,
798 unstable_flags,
799 build_info,
800 )?;
801 }
802 (opt_result, Some(metadata_result), Some(dest_wasm))
803 }
804 };
805
806 Ok(BuildResult {
807 dest_wasm,
808 metadata_result,
809 target_directory: crate_metadata.target_directory,
810 optimization_result: opt_result,
811 build_mode: *build_mode,
812 build_artifact: *build_artifact,
813 verbosity: *verbosity,
814 image: None,
815 output_type: output_type.clone(),
816 })
817}
818
819fn local_build(
821 crate_metadata: &CrateMetadata,
822 optimization_passes: &OptimizationPasses,
823 args: &ExecuteArgs,
824) -> Result<(Option<OptimizationResult>, BuildInfo, PathBuf)> {
825 let ExecuteArgs {
826 verbosity,
827 features,
828 build_mode,
829 network,
830 unstable_flags,
831 keep_debug_symbols,
832 extra_lints,
833 skip_wasm_validation,
834 target,
835 max_memory_pages,
836 ..
837 } = args;
838
839 lint(*extra_lints, crate_metadata, target, verbosity)?;
842
843 let pre_fingerprint = Fingerprint::new(crate_metadata)?;
844
845 verbose_eprintln!(
846 verbosity,
847 " {} {}",
848 "[==]".bold(),
849 "Building cargo project".bright_green().bold()
850 );
851 check_buffer_size_invoke_cargo_clean(crate_metadata, verbosity)?;
852 exec_cargo_for_onchain_target(
853 crate_metadata,
854 "build",
855 features,
856 build_mode,
857 network,
858 verbosity,
859 unstable_flags,
860 target,
861 )?;
862
863 fs::write(&crate_metadata.target_file_path, target.llvm_target())?;
865
866 let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) {
867 version
868 } else {
869 anyhow::bail!(
870 "Unable to parse version number for the currently running \
871 `cargo-contract` binary."
872 );
873 };
874
875 let build_info = BuildInfo {
876 rust_toolchain: util::rust_toolchain()?,
877 cargo_contract_version,
878 build_mode: *build_mode,
879 wasm_opt_settings: WasmOptSettings {
880 optimization_passes: *optimization_passes,
881 keep_debug_symbols: *keep_debug_symbols,
882 },
883 };
884
885 let post_fingerprint = Fingerprint::new(crate_metadata)?.ok_or_else(|| {
886 anyhow::anyhow!(
887 "Expected '{}' to be generated by build",
888 crate_metadata.original_code.display()
889 )
890 })?;
891
892 tracing::debug!(
893 "Fingerprint before build: {:?}, after build: {:?}",
894 pre_fingerprint,
895 post_fingerprint
896 );
897
898 let dest_code_path = crate_metadata.dest_code.clone();
899
900 if pre_fingerprint == Some(post_fingerprint) && crate_metadata.dest_code.exists() {
901 tracing::info!(
902 "No changes in the original wasm at {}, fingerprint {:?}. \
903 Skipping Wasm optimization and metadata generation.",
904 crate_metadata.original_code.display(),
905 pre_fingerprint
906 );
907 return Ok((None, build_info, dest_code_path))
908 }
909
910 verbose_eprintln!(
911 verbosity,
912 " {} {}",
913 "[==]".bold(),
914 "Post processing code".bright_green().bold()
915 );
916
917 for t in Target::iter() {
919 fs::remove_file(crate_metadata.dest_code.with_extension(t.dest_extension())).ok();
920 }
921
922 let original_size =
923 fs::metadata(&crate_metadata.original_code)?.len() as f64 / 1000.0;
924
925 match target {
926 Target::Wasm => {
927 let handler = WasmOptHandler::new(*optimization_passes, *keep_debug_symbols)?;
928 handler.optimize(&crate_metadata.original_code, &crate_metadata.dest_code)?;
929 post_process_wasm(
930 &crate_metadata.dest_code,
931 *skip_wasm_validation,
932 verbosity,
933 *max_memory_pages,
934 )?;
935 }
936 Target::RiscV => {
937 fs::copy(&crate_metadata.original_code, &crate_metadata.dest_code)?;
938 }
939 }
940
941 let optimized_size = fs::metadata(&dest_code_path)?.len() as f64 / 1000.0;
942
943 let optimization_result = OptimizationResult {
944 original_size,
945 optimized_size,
946 };
947
948 Ok((
949 Some(optimization_result),
950 build_info,
951 crate_metadata.dest_code.clone(),
952 ))
953}
954
955#[derive(Debug, Eq, PartialEq)]
957struct Fingerprint {
958 path: PathBuf,
959 hash: [u8; 32],
960 modified: std::time::SystemTime,
961 target: String,
962}
963
964impl Fingerprint {
965 fn new(crate_metadata: &CrateMetadata) -> Result<Option<Fingerprint>> {
966 let code_path = &crate_metadata.original_code;
967 let target_path = &crate_metadata.target_file_path;
968 if code_path.exists() {
969 let modified = fs::metadata(code_path)?.modified()?;
970 let bytes = fs::read(code_path)?;
971 let hash = blake2_hash(&bytes);
972 Ok(Some(Self {
973 path: code_path.clone(),
974 hash,
975 modified,
976 target: fs::read_to_string(target_path).with_context(|| {
977 format!(
978 "Cannot read {}.\n A clean build will fix this.",
979 target_path.display()
980 )
981 })?,
982 }))
983 } else {
984 Ok(None)
985 }
986 }
987}
988
989pub fn code_hash(code: &[u8]) -> [u8; 32] {
991 blake2_hash(code)
992}
993
994fn blake2_hash(code: &[u8]) -> [u8; 32] {
996 use blake2::digest::{
997 consts::U32,
998 Digest as _,
999 };
1000 let mut blake2 = blake2::Blake2b::<U32>::new();
1001 blake2.update(code);
1002 let result = blake2.finalize();
1003 result.into()
1004}
1005
1006#[cfg(test)]
1009mod unit_tests {
1010 use super::*;
1011 use crate::Verbosity;
1012 use semver::Version;
1013
1014 #[test]
1015 pub fn debug_mode_must_be_compatible() {
1016 assert_debug_mode_supported(
1017 &Version::parse("3.0.0-rc4").expect("parsing must work"),
1018 )
1019 .expect("debug mode must be compatible");
1020 assert_debug_mode_supported(
1021 &Version::parse("4.0.0-rc1").expect("parsing must work"),
1022 )
1023 .expect("debug mode must be compatible");
1024 assert_debug_mode_supported(&Version::parse("5.0.0").expect("parsing must work"))
1025 .expect("debug mode must be compatible");
1026 }
1027
1028 #[test]
1029 pub fn debug_mode_must_be_incompatible() {
1030 let res = assert_debug_mode_supported(
1031 &Version::parse("3.0.0-rc3").expect("parsing must work"),
1032 )
1033 .expect_err("assertion must fail");
1034 assert_eq!(
1035 res.to_string(),
1036 "Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
1037 );
1038 }
1039
1040 #[test]
1041 fn build_result_seralization_sanity_check() {
1042 let raw_result = r#"{
1044 "dest_wasm": "/path/to/contract.wasm",
1045 "metadata_result": {
1046 "dest_metadata": "/path/to/contract.json",
1047 "dest_bundle": "/path/to/contract.contract"
1048 },
1049 "target_directory": "/path/to/target",
1050 "optimization_result": {
1051 "original_size": 64.0,
1052 "optimized_size": 32.0
1053 },
1054 "build_mode": "Debug",
1055 "build_artifact": "All",
1056 "verbosity": "Quiet",
1057 "image": null
1058}"#;
1059
1060 let build_result = BuildResult {
1061 dest_wasm: Some(PathBuf::from("/path/to/contract.wasm")),
1062 metadata_result: Some(MetadataArtifacts {
1063 dest_metadata: PathBuf::from("/path/to/contract.json"),
1064 dest_bundle: PathBuf::from("/path/to/contract.contract"),
1065 }),
1066 target_directory: PathBuf::from("/path/to/target"),
1067 optimization_result: Some(OptimizationResult {
1068 original_size: 64.0,
1069 optimized_size: 32.0,
1070 }),
1071 build_mode: Default::default(),
1072 build_artifact: Default::default(),
1073 image: None,
1074 verbosity: Verbosity::Quiet,
1075 output_type: OutputType::Json,
1076 };
1077
1078 let serialized_result = build_result.serialize_json();
1080
1081 assert!(serialized_result.is_ok());
1083 assert_eq!(serialized_result.unwrap(), raw_result);
1084 }
1085}