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