1#![doc = include_str!("../README.md")]
18#![deny(unused_crate_dependencies)]
19
20use contract_metadata::{
21 ContractMetadata,
22 compatibility::check_contract_ink_compatibility,
23};
24pub use lint::lint;
25use rustversion::{
26 before,
27 since,
28};
29
30use generic_array as _;
34
35mod args;
36mod crate_metadata;
37mod docker;
38mod lint;
39pub mod metadata;
40mod new;
41mod solidity_metadata;
42#[cfg(test)]
43mod tests;
44pub mod util;
45mod validate_bytecode;
46mod workspace;
47
48#[deprecated(since = "2.0.2", note = "Use MetadataArtifacts instead")]
49pub use self::metadata::InkMetadataArtifacts as MetadataResult;
50
51pub use self::{
52 args::{
53 BuildArtifacts,
54 BuildMode,
55 Features,
56 MetadataSpec,
57 Network,
58 OutputType,
59 Target,
60 UnstableFlags,
61 UnstableOptions,
62 Verbosity,
63 VerbosityFlags,
64 },
65 crate_metadata::CrateMetadata,
66 docker::ComposeBuildArgs,
67 metadata::{
68 BuildInfo,
69 InkMetadataArtifacts,
70 MetadataArtifacts,
71 },
72 new::new_contract_project,
73 solidity_metadata::SolidityMetadataArtifacts,
74 util::DEFAULT_KEY_COL_WIDTH,
75 workspace::{
76 Lto,
77 Manifest,
78 ManifestPath,
79 OptLevel,
80 PanicStrategy,
81 Profile,
82 Workspace,
83 },
84};
85
86use std::{
87 cmp::PartialEq,
88 fmt,
89 fs,
90 path::PathBuf,
91 str,
92};
93
94use anyhow::{
95 Context,
96 Result,
97 bail,
98};
99use colored::Colorize;
100pub use docker::{
101 ImageVariant,
102 docker_build,
103};
104use regex::Regex;
105use semver::Version;
106
107const VERSION: &str = env!("CARGO_PKG_VERSION");
109
110#[since(1.92)]
111const PANIC_IMMEDIATE_ABORT: &str = "-Zunstable-options\x1f-Cpanic=immediate-abort";
112#[before(1.92)]
113const PANIC_IMMEDIATE_ABORT: &str = "";
114
115#[derive(serde::Serialize, serde::Deserialize)]
117pub struct LinkerSizeResult {
118 pub original_size: f64,
120 pub optimized_size: f64,
122}
123
124#[derive(Default, 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 keep_debug_symbols: bool,
136 pub extra_lints: bool,
137 pub output_type: OutputType,
138 pub image: ImageVariant,
139 pub metadata_spec: Option<MetadataSpec>,
140 pub target_dir: Option<PathBuf>,
141}
142
143#[derive(serde::Serialize, serde::Deserialize)]
145pub struct BuildResult {
146 pub dest_binary: Option<PathBuf>,
148 pub metadata_result: Option<MetadataArtifacts>,
150 pub target_directory: PathBuf,
152 pub linker_size_result: Option<LinkerSizeResult>,
154 pub build_mode: BuildMode,
156 pub build_artifact: BuildArtifacts,
158 pub verbosity: Verbosity,
160 pub image: Option<String>,
162 #[serde(skip_serializing, skip_deserializing)]
164 pub output_type: OutputType,
165}
166
167impl BuildResult {
168 pub fn display(&self) -> String {
169 let opt_size_diff = if let Some(ref opt_result) = self.linker_size_result {
170 let size_diff = format!(
171 "\nOriginal size: {}, Optimized: {}\n\n",
172 format!("{:.1}K", opt_result.original_size).bold(),
173 format!("{:.1}K", opt_result.optimized_size).bold(),
174 );
175 debug_assert!(
176 opt_result.optimized_size > 0.0,
177 "optimized file size must be greater 0"
178 );
179 size_diff
180 } else {
181 "\n".to_string()
182 };
183
184 let build_mode = format!(
185 "The contract was built in {} mode.\n\n",
186 format!("{}", self.build_mode).to_uppercase().bold(),
187 );
188
189 if self.build_artifact == BuildArtifacts::CodeOnly {
190 let out = format!(
191 "{}{}Your contract's code is ready. You can find it here:\n{}",
192 opt_size_diff,
193 build_mode,
194 self.dest_binary
195 .as_ref()
196 .expect("polkavm path must exist")
197 .display()
198 .to_string()
199 .bold()
200 );
201 return out
202 };
203
204 let mut out = format!(
205 "{}{}Your contract artifacts are ready. You can find them in:\n{}\n\n",
206 opt_size_diff,
207 build_mode,
208 self.target_directory.display().to_string().bold(),
209 );
210 if let Some(metadata_result) = self.metadata_result.as_ref() {
211 let (dest, desc) = match metadata_result {
212 MetadataArtifacts::Ink(ink_metadata_artifacts) => {
213 (&ink_metadata_artifacts.dest_bundle, "code + metadata")
214 }
215 MetadataArtifacts::Solidity(solidity_metadata_artifacts) => {
216 (
217 &solidity_metadata_artifacts.dest_metadata,
218 "Solidity compatible metadata",
219 )
220 }
221 };
222 let bundle = format!(" - {} ({})\n", util::base_name(dest).bold(), desc);
223 out.push_str(&bundle);
224 }
225 if let Some(dest_binary) = self.dest_binary.as_ref() {
226 let path = format!(
227 " - {} (the contract's code)\n",
228 util::base_name(dest_binary).bold()
229 );
230 out.push_str(&path);
231 }
232 if let Some(metadata_result) = self.metadata_result.as_ref() {
233 let (dest, desc) = match metadata_result {
234 MetadataArtifacts::Ink(ink_metadata_artifacts) => {
235 (&ink_metadata_artifacts.dest_metadata, "metadata")
236 }
237 MetadataArtifacts::Solidity(solidity_metadata_artifacts) => {
238 (
239 &solidity_metadata_artifacts.dest_abi,
240 "Solidity compatible ABI",
241 )
242 }
243 };
244 let metadata = format!(
245 " - {} (the contract's {})",
246 util::base_name(dest).bold(),
247 desc
248 );
249 out.push_str(&metadata);
250 }
251 out
252 }
253
254 pub fn serialize_json(&self) -> Result<String> {
256 Ok(serde_json::to_string_pretty(self)?)
257 }
258}
259
260#[derive(
262 Debug,
263 Clone,
264 Copy,
265 PartialEq,
266 Eq,
267 Default,
268 clap::ValueEnum,
269 serde::Serialize,
270 serde::Deserialize,
271)]
272#[serde(rename_all = "lowercase")]
273pub enum Abi {
274 #[default]
276 #[clap(name = "ink")]
277 #[serde(rename = "ink")]
278 Ink,
279 #[clap(name = "sol")]
281 #[serde(rename = "sol")]
282 Solidity,
283 #[clap(name = "all")]
285 All,
286}
287
288impl AsRef<str> for Abi {
289 fn as_ref(&self) -> &str {
290 match self {
291 Self::Ink => "ink",
292 Self::Solidity => "sol",
293 Self::All => "all",
294 }
295 }
296}
297
298impl fmt::Display for Abi {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "{}", self.as_ref())
301 }
302}
303
304impl Abi {
305 pub fn rustflag(&self) -> String {
307 format!(
308 "--cfg ink_abi=\"{self}\" --check-cfg cfg(ink_abi,values(\"ink\",\"sol\",\"all\"))"
309 )
310 }
311
312 pub fn cargo_encoded_rustflag(&self) -> String {
315 format!(
316 "--cfg\x1fink_abi=\"{self}\"\x1f--check-cfg\x1fcfg(ink_abi,values(\"ink\",\"sol\",\"all\"))"
317 )
318 }
319
320 fn metadata_spec(&self) -> MetadataSpec {
322 match self {
323 Abi::Ink | Abi::All => MetadataSpec::Ink,
324 Abi::Solidity => MetadataSpec::Solidity,
325 }
326 }
327}
328
329#[allow(clippy::too_many_arguments)]
351fn exec_cargo_for_onchain_target(
352 crate_metadata: &CrateMetadata,
353 command: &str,
354 features: &Features,
355 build_mode: &BuildMode,
356 network: &Network,
357 verbosity: &Verbosity,
358 unstable_flags: &UnstableFlags,
359) -> Result<()> {
360 let cargo_build = |manifest_path: &ManifestPath| {
361 let target_dir = format!(
362 "--target-dir={}",
363 crate_metadata.target_directory.to_string_lossy()
364 );
365
366 let mut args = vec![
367 format!("--target={}", Target::llvm_target(crate_metadata)),
368 "--release".to_owned(),
369 target_dir,
370 ];
371 args.extend(onchain_cargo_options(crate_metadata));
372 network.append_to_args(&mut args);
373
374 let mut features = features.clone();
375 if build_mode == &BuildMode::Debug {
376 features.push("ink/ink-debug".to_string());
377 } else {
378 #[since(1.92)]
379 fn set_args(args: &mut Vec<String>) {
380 args.push("-Zbuild-std-features=compiler-builtins-mem".to_owned());
381 }
382 #[before(1.92)]
383 fn set_args(args: &mut Vec<String>) {
384 args.push(
385 "-Zbuild-std-features=panic_immediate_abort,compiler-builtins-mem"
386 .to_owned(),
387 );
388 }
389 set_args(&mut args);
390 }
391 features.append_to_args(&mut args);
392 let mut env = Vec::new();
393 if rustc_version::version_meta()?.channel == rustc_version::Channel::Stable {
394 env.push(("RUSTC_BOOTSTRAP", Some("1".to_string())))
396 }
397
398 let mut rustflags = {
406 let common_flags = "-Clinker-plugin-lto\x1f-Clink-arg=-zstack-size=4096";
407 let mut flags = if let Some(target_flags) = Target::rustflags() {
408 format!("{common_flags}\x1f{target_flags}")
409 } else {
410 common_flags.to_string()
411 };
412 #[allow(clippy::const_is_empty)]
417 if build_mode != &BuildMode::Debug && !PANIC_IMMEDIATE_ABORT.is_empty() {
418 flags.push('\x1f');
419 flags.push_str(PANIC_IMMEDIATE_ABORT);
420 }
421 flags
422 };
423 if let Some(abi) = crate_metadata.abi {
425 rustflags.push('\x1f');
426 let abi_cfg_flags = abi.cargo_encoded_rustflag();
427 rustflags.push_str(&abi_cfg_flags);
428
429 if let Some(rustc_wrapper_envs) =
434 util::rustc_wrapper::env_vars(crate_metadata)?
435 {
436 env.extend(rustc_wrapper_envs);
437 }
438 }
439 env.push(("CARGO_ENCODED_RUSTFLAGS", Some(rustflags)));
441
442 fs::create_dir_all(&crate_metadata.artifact_directory)?;
443
444 let cmd =
445 util::cargo_cmd(command, &args, manifest_path.directory(), *verbosity, env);
446 tracing::debug!("Executing '{:#?}'", cmd);
447 execute_cargo(cmd)
448 };
449
450 if unstable_flags.original_manifest {
451 verbose_eprintln!(
452 verbosity,
453 "{} {}",
454 "warning:".yellow().bold(),
455 "with 'original-manifest' enabled, the contract binary may not be of optimal size."
456 .bold()
457 );
458 cargo_build(&crate_metadata.manifest_path)?;
459 } else {
460 Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
461 .with_root_package_manifest(|manifest| {
462 manifest
463 .with_replaced_lib_to_bin()?
464 .with_profile_release_defaults(Profile::default_contract_release())?
465 .with_merged_workspace_dependencies(crate_metadata)?
466 .with_empty_workspace();
467 Ok(())
468 })?
469 .using_temp(cargo_build)?;
470 }
471
472 Ok(())
473}
474
475fn check_buffer_size_invoke_cargo_clean(
479 crate_metadata: &CrateMetadata,
480 verbosity: &Verbosity,
481) -> Result<()> {
482 if let Ok(buffer_size) = std::env::var("INK_STATIC_BUFFER_SIZE") {
483 let buffer_size_value: u64 = buffer_size
484 .parse()
485 .context("`INK_STATIC_BUFFER_SIZE` must have an integer value.")?;
486
487 let extract_buffer_size = |metadata_path: PathBuf| -> Result<u64> {
488 let size = ContractMetadata::load(metadata_path)
489 .context("Metadata is not present")?
490 .abi
491 .get("spec")
493 .context("spec field should be present in ABI.")?
494 .get("environment")
496 .context("environment field should be present in ABI.")?
497 .get("staticBufferSize")
499 .context("`staticBufferSize` must be specified.")?
500 .as_u64()
502 .context("`staticBufferSize` value must be an integer.")?;
503
504 Ok(size)
505 };
506
507 let cargo = util::cargo_cmd(
508 "clean",
509 Vec::<&str>::new(),
510 crate_metadata.manifest_path.directory(),
511 *verbosity,
512 vec![],
513 );
514
515 match extract_buffer_size(crate_metadata.metadata_path()) {
516 Ok(contract_buffer_size) if contract_buffer_size == buffer_size_value => {
517 verbose_eprintln!(
518 verbosity,
519 "{} {}",
520 "info:".green().bold(),
521 "Detected a configured buffer size, but the value is already specified."
522 .bold()
523 );
524 }
525 Ok(_) => {
526 verbose_eprintln!(
527 verbosity,
528 "{} {}",
529 "warning:".yellow().bold(),
530 "Detected a change in the configured buffer size. Rebuilding the project."
531 .bold()
532 );
533 execute_cargo(cargo)?;
534 }
535 Err(_) => {
536 verbose_eprintln!(
537 verbosity,
538 "{} {}",
539 "warning:".yellow().bold(),
540 "Cannot find the previous size of the static buffer. Rebuilding the project."
541 .bold()
542 );
543 execute_cargo(cargo)?;
544 }
545 }
546 }
547 Ok(())
548}
549
550fn execute_cargo(cargo: duct::Expression) -> Result<()> {
553 match cargo.unchecked().run() {
554 Ok(out) if out.status.success() => Ok(()),
555 Ok(out) => anyhow::bail!(String::from_utf8_lossy(&out.stderr).to_string()),
556 Err(e) => anyhow::bail!("Cannot run `cargo` command: {e:?}"),
557 }
558}
559
560fn onchain_cargo_options(crate_metadata: &CrateMetadata) -> Vec<String> {
562 vec![
563 format!("--target={}", Target::llvm_target(crate_metadata)),
564 "-Zbuild-std=core,alloc".to_owned(),
565 "--no-default-features".to_owned(),
566 ]
567}
568
569pub fn assert_debug_mode_supported(ink_version: &Version) -> Result<()> {
573 tracing::debug!("Contract version: {:?}", ink_version);
574 let minimum_version = Version::parse("3.0.0-rc4").expect("parsing version failed");
575 if ink_version < &minimum_version {
576 anyhow::bail!(
577 "Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
578 );
579 }
580 Ok(())
581}
582
583fn validate_metadata_spec_for_abi(
586 metadata_spec: &MetadataSpec,
587 abi: Option<&Abi>,
588) -> Result<()> {
589 match (abi, metadata_spec) {
590 (None | Some(Abi::Ink) | Some(Abi::All), MetadataSpec::Ink)
591 | (Some(Abi::Solidity) | Some(Abi::All), MetadataSpec::Solidity) => Ok(()),
592 _ => {
593 Err(anyhow::anyhow!(
594 "Unsupported metadata format `{}` for ABI `{}`",
595 metadata_spec,
596 abi.cloned().unwrap_or_default()
597 ))
598 }
599 }
600}
601
602pub fn execute<T: ComposeBuildArgs>(args: ExecuteArgs) -> Result<BuildResult> {
607 let ExecuteArgs {
608 manifest_path,
609 verbosity,
610 features,
611 build_mode,
612 network,
613 build_artifact,
614 unstable_flags,
615 extra_lints,
616 output_type,
617 target_dir,
618 ..
619 } = &args;
620
621 if build_mode == &BuildMode::Verifiable {
623 return docker_build::<T>(args)
624 }
625
626 let crate_metadata =
627 CrateMetadata::collect_with_target_dir(manifest_path, target_dir.clone())?;
628
629 if build_mode == &BuildMode::Debug {
630 assert_debug_mode_supported(&crate_metadata.ink_version)?;
631 }
632
633 if let Err(e) = check_contract_ink_compatibility(&crate_metadata.ink_version, None) {
634 eprintln!("{} {}", "warning:".yellow().bold(), e.to_string().bold());
635 }
636
637 let clean_metadata = || {
638 fs::remove_file(crate_metadata.metadata_path()).ok();
639 fs::remove_file(crate_metadata.contract_bundle_path()).ok();
640 fs::remove_file(solidity_metadata::abi_path(&crate_metadata)).ok();
641 fs::remove_file(solidity_metadata::metadata_path(&crate_metadata)).ok();
642 };
643
644 let (opt_result, metadata_result, dest_binary) = match build_artifact {
645 BuildArtifacts::CheckOnly => {
646 lint(*extra_lints, &crate_metadata, verbosity)?;
648 (None, None, None)
649 }
650 BuildArtifacts::CodeOnly => {
651 clean_metadata();
653 let (opt_result, _, dest_binary) = local_build(&crate_metadata, &args)?;
654 (opt_result, None, Some(dest_binary))
655 }
656 BuildArtifacts::All => {
657 let metadata_spec = args.metadata_spec.unwrap_or_else(|| {
659 crate_metadata.abi.unwrap_or_default().metadata_spec()
660 });
661 validate_metadata_spec_for_abi(&metadata_spec, crate_metadata.abi.as_ref())?;
662
663 let (opt_result, build_info, dest_binary) =
664 local_build(&crate_metadata, &args).inspect_err(|_| {
665 clean_metadata();
667 })?;
668
669 let metadata_artifacts = match metadata_spec {
670 MetadataSpec::Ink => {
671 MetadataArtifacts::Ink(InkMetadataArtifacts {
672 dest_metadata: crate_metadata.metadata_path(),
673 dest_bundle: crate_metadata.contract_bundle_path(),
674 })
675 }
676 MetadataSpec::Solidity => {
677 MetadataArtifacts::Solidity(SolidityMetadataArtifacts {
678 dest_abi: solidity_metadata::abi_path(&crate_metadata),
679 dest_metadata: solidity_metadata::metadata_path(&crate_metadata),
680 })
681 }
682 };
683
684 let pre_metadata_spec =
687 fs::read_to_string(&crate_metadata.metadata_spec_path);
688 let is_unchanged_metadata_spec =
689 pre_metadata_spec.ok() == Some(metadata_spec.to_string());
690 if opt_result.is_some()
691 || !is_unchanged_metadata_spec
692 || !metadata_artifacts.exists()
693 {
694 if !is_unchanged_metadata_spec {
697 fs::write(
698 &crate_metadata.metadata_spec_path,
699 metadata_spec.to_string(),
700 )?;
701 }
702
703 clean_metadata();
705 metadata::execute(
706 &crate_metadata,
707 dest_binary.as_path(),
708 &metadata_artifacts,
709 features,
710 *network,
711 *verbosity,
712 unstable_flags,
713 build_info,
714 )?;
715 }
716 (opt_result, Some(metadata_artifacts), Some(dest_binary))
717 }
718 };
719
720 Ok(BuildResult {
721 dest_binary,
722 metadata_result,
723 target_directory: crate_metadata.artifact_directory,
724 linker_size_result: opt_result,
725 build_mode: *build_mode,
726 build_artifact: *build_artifact,
727 verbosity: *verbosity,
728 image: None,
729 output_type: output_type.clone(),
730 })
731}
732
733fn local_build(
735 crate_metadata: &CrateMetadata,
736 args: &ExecuteArgs,
737) -> Result<(Option<LinkerSizeResult>, BuildInfo, PathBuf)> {
738 let ExecuteArgs {
739 verbosity,
740 features,
741 build_mode,
742 network,
743 unstable_flags,
744 ..
745 } = args;
746
747 let pre_fingerprint = Fingerprint::new(crate_metadata)?;
748
749 verbose_eprintln!(
750 verbosity,
751 " {} {}",
752 "[==]".bold(),
753 "Building cargo project".bright_green().bold()
754 );
755 check_buffer_size_invoke_cargo_clean(crate_metadata, verbosity)?;
756 exec_cargo_for_onchain_target(
757 crate_metadata,
758 "build",
759 features,
760 build_mode,
761 network,
762 verbosity,
763 unstable_flags,
764 )?;
765
766 fs::write(
768 &crate_metadata.target_file_path,
769 Target::llvm_target(crate_metadata),
770 )?;
771
772 let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) {
773 version
774 } else {
775 anyhow::bail!(
776 "Unable to parse version number for the currently running \
777 `cargo-contract` binary."
778 );
779 };
780
781 let build_info = BuildInfo {
782 rust_toolchain: util::rust_toolchain()?,
783 cargo_contract_version,
784 build_mode: *build_mode,
785 };
786
787 let post_fingerprint = Fingerprint::new(crate_metadata)?.ok_or_else(|| {
788 anyhow::anyhow!(
789 "Expected '{}' to be generated by build",
790 crate_metadata.original_code.display()
791 )
792 })?;
793
794 tracing::debug!(
795 "Fingerprint before build: {:?}, after build: {:?}",
796 pre_fingerprint,
797 post_fingerprint
798 );
799
800 let dest_code_path = crate_metadata.dest_binary.clone();
801
802 if pre_fingerprint == Some(post_fingerprint) && crate_metadata.dest_binary.exists() {
803 tracing::info!(
804 "No changes in the original PolkaVM binary at {}, fingerprint {:?}. \
805 Skipping metadata generation.",
806 crate_metadata.original_code.display(),
807 pre_fingerprint
808 );
809 return Ok((None, build_info, dest_code_path))
810 }
811
812 verbose_eprintln!(
813 verbosity,
814 " {} {}",
815 "[==]".bold(),
816 "Post processing code".bright_green().bold()
817 );
818
819 fs::remove_file(
821 crate_metadata
822 .dest_binary
823 .with_extension(Target::dest_extension()),
824 )
825 .ok();
826
827 let original_size =
828 fs::metadata(&crate_metadata.original_code)?.len() as f64 / 1000.0;
829
830 let mut config = polkavm_linker::Config::default();
831 config.set_strip(true);
832 config.set_optimize(true);
833 let orig = fs::read(&crate_metadata.original_code)?;
834
835 let linked = match polkavm_linker::program_from_elf(
836 config,
837 polkavm_linker::TargetInstructionSet::ReviveV1,
838 orig.as_ref(),
839 ) {
840 Ok(linked) => linked,
841 Err(err) => {
842 let re =
843 Regex::new(r"'(__ink_enforce_error_.*)'").expect("failed creating regex");
844 let err = err.to_string();
845 let mut ink_err = re.captures_iter(&err).map(|c| c.extract());
846 let mut details = String::from("");
847 if let Some((_, [ink_err_identifier])) = ink_err.next() {
848 details = format!(
849 "\n\n{}",
850 validate_bytecode::parse_linker_error(ink_err_identifier)
851 );
852 }
853 bail!("Failed to link polkavm program: {err}{details}")
854 }
855 };
856 fs::write(&crate_metadata.dest_binary, linked)?;
857
858 let optimized_size = fs::metadata(&dest_code_path)?.len() as f64 / 1000.0;
859
860 let optimization_result = LinkerSizeResult {
861 original_size,
862 optimized_size,
863 };
864
865 Ok((
866 Some(optimization_result),
867 build_info,
868 crate_metadata.dest_binary.clone(),
869 ))
870}
871
872#[derive(Debug, Eq, PartialEq)]
874struct Fingerprint {
875 path: PathBuf,
876 hash: [u8; 32],
877 modified: std::time::SystemTime,
878 target: String,
879}
880
881impl Fingerprint {
882 fn new(crate_metadata: &CrateMetadata) -> Result<Option<Fingerprint>> {
883 let code_path = &crate_metadata.original_code;
884 let target_path = &crate_metadata.target_file_path;
885 if code_path.exists() {
886 let modified = fs::metadata(code_path)?.modified()?;
887 let bytes = fs::read(code_path)?;
888 let hash = blake2_hash(&bytes);
889 Ok(Some(Self {
890 path: code_path.clone(),
891 hash,
892 modified,
893 target: fs::read_to_string(target_path).with_context(|| {
894 format!(
895 "Cannot read {}.\n A clean build will fix this.",
896 target_path.display()
897 )
898 })?,
899 }))
900 } else {
901 Ok(None)
902 }
903 }
904}
905
906pub fn code_hash(code: &[u8]) -> [u8; 32] {
908 h256_hash(code)
909}
910
911fn h256_hash(code: &[u8]) -> [u8; 32] {
913 use sha3::{
914 Digest,
915 Keccak256,
916 };
917 let hash = Keccak256::digest(code);
918 let sl: &[u8] = hash.as_ref();
919 assert!(sl.len() == 32, "expected length of 32");
920 let mut arr = [0u8; 32];
921 arr.copy_from_slice(sl);
922 arr
923}
924
925fn blake2_hash(code: &[u8]) -> [u8; 32] {
927 use blake2::digest::{
928 Digest as _,
929 consts::U32,
930 };
931 let mut blake2 = blake2::Blake2b::<U32>::new();
932 blake2.update(code);
933 let result = blake2.finalize();
934 result.into()
935}
936
937pub fn project_path(path: PathBuf) -> PathBuf {
939 if let Ok(cargo_target_dir) = std::env::var("CARGO_TARGET_DIR") {
940 PathBuf::from(cargo_target_dir)
941 } else {
942 path
943 }
944}
945
946#[cfg(test)]
949mod unit_tests {
950 use super::*;
951 use crate::{
952 Verbosity,
953 util::tests::with_tmp_dir,
954 };
955 use semver::Version;
956
957 #[test]
958 pub fn debug_mode_must_be_compatible() {
959 assert_debug_mode_supported(
960 &Version::parse("3.0.0-rc4").expect("parsing must work"),
961 )
962 .expect("debug mode must be compatible");
963 assert_debug_mode_supported(
964 &Version::parse("4.0.0-rc1").expect("parsing must work"),
965 )
966 .expect("debug mode must be compatible");
967 assert_debug_mode_supported(&Version::parse("5.0.0").expect("parsing must work"))
968 .expect("debug mode must be compatible");
969 }
970
971 #[test]
972 pub fn debug_mode_must_be_incompatible() {
973 let res = assert_debug_mode_supported(
974 &Version::parse("3.0.0-rc3").expect("parsing must work"),
975 )
976 .expect_err("assertion must fail");
977 assert_eq!(
978 res.to_string(),
979 "Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
980 );
981 }
982
983 #[test]
984 fn build_result_seralization_sanity_check() {
985 let raw_result = r#"{
987 "dest_binary": "/path/to/contract.polkavm",
988 "metadata_result": {
989 "Ink": {
990 "dest_metadata": "/path/to/contract.json",
991 "dest_bundle": "/path/to/contract.contract"
992 }
993 },
994 "target_directory": "/path/to/target",
995 "linker_size_result": {
996 "original_size": 64.0,
997 "optimized_size": 32.0
998 },
999 "build_mode": "Debug",
1000 "build_artifact": "All",
1001 "verbosity": "Quiet",
1002 "image": null
1003}"#;
1004
1005 let build_result = BuildResult {
1006 dest_binary: Some(PathBuf::from("/path/to/contract.polkavm")),
1007 metadata_result: Some(MetadataArtifacts::Ink(InkMetadataArtifacts {
1008 dest_metadata: PathBuf::from("/path/to/contract.json"),
1009 dest_bundle: PathBuf::from("/path/to/contract.contract"),
1010 })),
1011 target_directory: PathBuf::from("/path/to/target"),
1012 linker_size_result: Some(LinkerSizeResult {
1013 original_size: 64.0,
1014 optimized_size: 32.0,
1015 }),
1016 build_mode: Default::default(),
1017 build_artifact: Default::default(),
1018 image: None,
1019 verbosity: Verbosity::Quiet,
1020 output_type: OutputType::Json,
1021 };
1022
1023 let serialized_result = build_result.serialize_json();
1025
1026 assert!(serialized_result.is_ok());
1028 assert_eq!(serialized_result.unwrap(), raw_result);
1029 }
1030
1031 #[test]
1032 pub fn valid_metadata_spec_for_abi_works() {
1033 fn test_project_with_config(metadata_spec: MetadataSpec, abi: Option<Abi>) {
1034 with_tmp_dir(|path| {
1035 let name = "project_with_valid_config";
1036 let dir = path.join(name);
1037 fs::create_dir_all(&dir).unwrap();
1038 let result = new_contract_project(name, Some(path.join(name)), abi);
1039 assert!(result.is_ok(), "Should succeed");
1040
1041 let manifest_path = ManifestPath::new(dir.join("Cargo.toml")).unwrap();
1042 let crate_metadata = CrateMetadata::collect(&manifest_path).unwrap();
1043
1044 let result = validate_metadata_spec_for_abi(
1045 &metadata_spec,
1046 crate_metadata.abi.as_ref(),
1047 );
1048 assert!(result.is_ok(), "Should validate");
1049
1050 Ok(())
1051 });
1052 }
1053
1054 test_project_with_config(MetadataSpec::Ink, None);
1056 test_project_with_config(MetadataSpec::Ink, Some(Abi::Ink));
1057 test_project_with_config(MetadataSpec::Ink, Some(Abi::All));
1058
1059 test_project_with_config(MetadataSpec::Solidity, Some(Abi::Solidity));
1061 test_project_with_config(MetadataSpec::Solidity, Some(Abi::All));
1062 }
1063
1064 #[test]
1065 pub fn invalid_metadata_spec_for_abi_fails() {
1066 fn test_project_with_config(
1067 metadata_spec: MetadataSpec,
1068 abi: Option<Abi>,
1069 expected_error: String,
1070 ) {
1071 with_tmp_dir(|path| {
1072 let name = "project_with_invalid_config";
1073 let dir = path.join(name);
1074 fs::create_dir_all(&dir).unwrap();
1075 let result = new_contract_project(name, Some(path.join(name)), abi);
1076 assert!(result.is_ok(), "Should succeed");
1077
1078 let manifest_path = ManifestPath::new(dir.join("Cargo.toml")).unwrap();
1079 let crate_metadata = CrateMetadata::collect(&manifest_path).unwrap();
1080
1081 let result = validate_metadata_spec_for_abi(
1082 &metadata_spec,
1083 crate_metadata.abi.as_ref(),
1084 );
1085 assert!(result.is_err(), "Should fail to validate");
1086
1087 let error = result.unwrap_err();
1088 assert_eq!(error.to_string(), expected_error);
1089
1090 Ok(())
1091 });
1092 }
1093
1094 fn error_msg(metadata_spec: MetadataSpec, abi: Abi) -> String {
1095 format!("Unsupported metadata format `{metadata_spec}` for ABI `{abi}`")
1096 }
1097
1098 test_project_with_config(
1100 MetadataSpec::Ink,
1101 Some(Abi::Solidity),
1102 error_msg(MetadataSpec::Ink, Abi::Solidity),
1103 );
1104
1105 test_project_with_config(
1107 MetadataSpec::Solidity,
1108 None,
1109 error_msg(MetadataSpec::Solidity, Abi::default()),
1110 );
1111 test_project_with_config(
1112 MetadataSpec::Solidity,
1113 Some(Abi::Ink),
1114 error_msg(MetadataSpec::Solidity, Abi::Ink),
1115 );
1116 }
1117}