1use alloy_json_abi::JsonAbi;
4use alloy_primitives::Bytes;
5use foundry_compilers_artifacts::{
6 hh::HardhatArtifact,
7 sourcemap::{SourceMap, SyntaxError},
8 BytecodeObject, CompactBytecode, CompactContract, CompactContractBytecode,
9 CompactContractBytecodeCow, CompactDeployedBytecode, Contract, FileToContractsMap, SourceFile,
10};
11use foundry_compilers_core::{
12 error::{Result, SolcError, SolcIoError},
13 utils::{self, strip_prefix_owned},
14};
15use path_slash::PathBufExt;
16use semver::Version;
17use serde::{de::DeserializeOwned, Deserialize, Serialize};
18use std::{
19 borrow::Cow,
20 collections::{btree_map::BTreeMap, HashMap, HashSet},
21 ffi::OsString,
22 fmt, fs,
23 hash::Hash,
24 ops::Deref,
25 path::{Path, PathBuf},
26};
27
28mod configurable;
29pub use configurable::*;
30
31mod hh;
32pub use hh::*;
33
34use crate::{
35 cache::{CachedArtifacts, CompilerCache},
36 output::{
37 contracts::VersionedContracts,
38 sources::{VersionedSourceFile, VersionedSourceFiles},
39 },
40 CompilerContract, ProjectPathsConfig,
41};
42
43#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
45pub struct ArtifactId {
46 pub path: PathBuf,
48 pub name: String,
49 pub source: PathBuf,
51 pub version: Version,
53 pub build_id: String,
55 pub profile: String,
56}
57
58impl ArtifactId {
59 pub fn slash_paths(&mut self) {
61 #[cfg(windows)]
62 {
63 self.path = self.path.to_slash_lossy().as_ref().into();
64 self.source = self.source.to_slash_lossy().as_ref().into();
65 }
66 }
67
68 pub fn with_slashed_paths(mut self) -> Self {
70 self.slash_paths();
71 self
72 }
73
74 pub fn strip_file_prefixes(&mut self, base: &Path) {
76 if let Ok(stripped) = self.source.strip_prefix(base) {
77 self.source = stripped.to_path_buf();
78 }
79 }
80
81 pub fn with_stripped_file_prefixes(mut self, base: &Path) -> Self {
83 self.strip_file_prefixes(base);
84 self
85 }
86
87 pub fn slug(&self) -> String {
92 format!("{}.json:{}", self.path.file_stem().unwrap().to_string_lossy(), self.name)
93 }
94
95 pub fn identifier(&self) -> String {
97 format!("{}:{}", self.source.display(), self.name)
98 }
99
100 pub fn slug_versioned(&self) -> String {
102 format!(
103 "{}.{}.{}.{}.json:{}",
104 self.path.file_stem().unwrap().to_string_lossy(),
105 self.version.major,
106 self.version.minor,
107 self.version.patch,
108 self.name
109 )
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct ArtifactFile<T> {
116 pub artifact: T,
118 pub file: PathBuf,
120 pub version: Version,
122 pub build_id: String,
123 pub profile: String,
124}
125
126impl<T: Serialize> ArtifactFile<T> {
127 pub fn write(&self) -> Result<()> {
129 trace!("writing artifact file {:?} {}", self.file, self.version);
130 utils::create_parent_dir_all(&self.file)?;
131 utils::write_json_file(&self.artifact, &self.file, 64 * 1024)
132 }
133}
134
135impl<T> ArtifactFile<T> {
136 pub fn join(&mut self, root: &Path) {
138 self.file = root.join(&self.file);
139 }
140
141 pub fn strip_prefix(&mut self, base: &Path) {
143 if let Ok(stripped) = self.file.strip_prefix(base) {
144 self.file = stripped.to_path_buf();
145 }
146 }
147}
148
149pub(crate) type ArtifactsMap<T> = FileToContractsMap<Vec<ArtifactFile<T>>>;
151
152#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct Artifacts<T>(pub ArtifactsMap<T>);
155
156impl<T> From<ArtifactsMap<T>> for Artifacts<T> {
157 fn from(m: ArtifactsMap<T>) -> Self {
158 Self(m)
159 }
160}
161
162impl<'a, T> IntoIterator for &'a Artifacts<T> {
163 type Item = (&'a PathBuf, &'a BTreeMap<String, Vec<ArtifactFile<T>>>);
164 type IntoIter =
165 std::collections::btree_map::Iter<'a, PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>>;
166
167 fn into_iter(self) -> Self::IntoIter {
168 self.0.iter()
169 }
170}
171
172impl<T> IntoIterator for Artifacts<T> {
173 type Item = (PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>);
174 type IntoIter =
175 std::collections::btree_map::IntoIter<PathBuf, BTreeMap<String, Vec<ArtifactFile<T>>>>;
176
177 fn into_iter(self) -> Self::IntoIter {
178 self.0.into_iter()
179 }
180}
181
182impl<T> Default for Artifacts<T> {
183 fn default() -> Self {
184 Self(Default::default())
185 }
186}
187
188impl<T> AsRef<ArtifactsMap<T>> for Artifacts<T> {
189 fn as_ref(&self) -> &ArtifactsMap<T> {
190 &self.0
191 }
192}
193
194impl<T> AsMut<ArtifactsMap<T>> for Artifacts<T> {
195 fn as_mut(&mut self) -> &mut ArtifactsMap<T> {
196 &mut self.0
197 }
198}
199
200impl<T> Deref for Artifacts<T> {
201 type Target = ArtifactsMap<T>;
202
203 fn deref(&self) -> &Self::Target {
204 &self.0
205 }
206}
207
208impl<T: Serialize> Artifacts<T> {
209 pub fn write_all(&self) -> Result<()> {
211 for artifact in self.artifact_files() {
212 artifact.write()?;
213 }
214 Ok(())
215 }
216}
217
218impl<T> Artifacts<T> {
219 pub fn slash_paths(&mut self) {
221 #[cfg(windows)]
222 {
223 self.0 = std::mem::take(&mut self.0)
224 .into_iter()
225 .map(|(path, files)| (PathBuf::from(path.to_slash_lossy().as_ref()), files))
226 .collect()
227 }
228 }
229
230 pub fn into_inner(self) -> ArtifactsMap<T> {
231 self.0
232 }
233
234 pub fn join_all(&mut self, root: &Path) -> &mut Self {
236 self.artifact_files_mut().for_each(|artifact| artifact.join(root));
237 self
238 }
239
240 pub fn strip_prefix_all(&mut self, base: &Path) -> &mut Self {
242 self.artifact_files_mut().for_each(|artifact| artifact.strip_prefix(base));
243 self
244 }
245
246 fn get_contract_artifact_files(&self, contract_name: &str) -> Option<&Vec<ArtifactFile<T>>> {
248 self.0.values().find_map(|all| all.get(contract_name))
249 }
250
251 pub fn find_artifact(
253 &self,
254 file: &Path,
255 contract_name: &str,
256 version: &Version,
257 ) -> Option<&ArtifactFile<T>> {
258 self.0
259 .get(file)
260 .and_then(|contracts| contracts.get(contract_name))
261 .and_then(|artifacts| artifacts.iter().find(|artifact| artifact.version == *version))
262 }
263
264 pub fn has_contract_artifact(&self, contract_name: &str, artifact_path: &Path) -> bool {
266 self.get_contract_artifact_files(contract_name)
267 .map(|artifacts| artifacts.iter().any(|artifact| artifact.file == artifact_path))
268 .unwrap_or_default()
269 }
270
271 pub fn has_artifact(&self, artifact_path: &Path) -> bool {
273 self.artifact_files().any(|artifact| artifact.file == artifact_path)
274 }
275
276 pub fn artifact_files(&self) -> impl Iterator<Item = &ArtifactFile<T>> {
278 self.0.values().flat_map(BTreeMap::values).flatten()
279 }
280
281 pub fn artifact_files_mut(&mut self) -> impl Iterator<Item = &mut ArtifactFile<T>> {
283 self.0.values_mut().flat_map(BTreeMap::values_mut).flatten()
284 }
285
286 pub fn artifacts<O: ArtifactOutput<Artifact = T>>(
290 &self,
291 ) -> impl Iterator<Item = (ArtifactId, &T)> + '_ {
292 self.0.iter().flat_map(|(source, contract_artifacts)| {
293 contract_artifacts.iter().flat_map(move |(_contract_name, artifacts)| {
294 artifacts.iter().filter_map(move |artifact| {
295 O::contract_name(&artifact.file).map(|name| {
296 (
297 ArtifactId {
298 path: PathBuf::from(&artifact.file),
299 name,
300 source: source.clone(),
301 version: artifact.version.clone(),
302 build_id: artifact.build_id.clone(),
303 profile: artifact.profile.clone(),
304 }
305 .with_slashed_paths(),
306 &artifact.artifact,
307 )
308 })
309 })
310 })
311 })
312 }
313
314 pub fn into_artifacts<O: ArtifactOutput<Artifact = T>>(
316 self,
317 ) -> impl Iterator<Item = (ArtifactId, T)> {
318 self.0.into_iter().flat_map(|(source, contract_artifacts)| {
319 contract_artifacts.into_iter().flat_map(move |(_contract_name, artifacts)| {
320 let source = source.clone();
321 artifacts.into_iter().filter_map(move |artifact| {
322 O::contract_name(&artifact.file).map(|name| {
323 (
324 ArtifactId {
325 path: PathBuf::from(&artifact.file),
326 name,
327 source: source.clone(),
328 version: artifact.version,
329 build_id: artifact.build_id.clone(),
330 profile: artifact.profile.clone(),
331 }
332 .with_slashed_paths(),
333 artifact.artifact,
334 )
335 })
336 })
337 })
338 })
339 }
340
341 pub fn artifacts_with_files(&self) -> impl Iterator<Item = (&PathBuf, &String, &T)> + '_ {
347 self.0.iter().flat_map(|(f, contract_artifacts)| {
348 contract_artifacts.iter().flat_map(move |(name, artifacts)| {
349 artifacts.iter().map(move |artifact| (f, name, &artifact.artifact))
350 })
351 })
352 }
353
354 pub fn into_artifacts_with_files(self) -> impl Iterator<Item = (PathBuf, String, T)> {
358 self.0.into_iter().flat_map(|(f, contract_artifacts)| {
359 contract_artifacts.into_iter().flat_map(move |(name, artifacts)| {
360 let contract_name = name;
361 let file = f.clone();
362 artifacts
363 .into_iter()
364 .map(move |artifact| (file.clone(), contract_name.clone(), artifact.artifact))
365 })
366 })
367 }
368
369 pub fn into_stripped_file_prefixes(self, base: &Path) -> Self {
372 let artifacts =
373 self.0.into_iter().map(|(path, c)| (strip_prefix_owned(path, base), c)).collect();
374 Self(artifacts)
375 }
376
377 pub fn find_first(&self, contract_name: &str) -> Option<&T> {
379 self.0.iter().find_map(|(_file, contracts)| {
380 contracts.get(contract_name).and_then(|c| c.first().map(|a| &a.artifact))
381 })
382 }
383
384 pub fn find(&self, contract_path: &Path, contract_name: &str) -> Option<&T> {
386 self.0.iter().filter(|(path, _)| path.as_path() == contract_path).find_map(
387 |(_file, contracts)| {
388 contracts.get(contract_name).and_then(|c| c.first().map(|a| &a.artifact))
389 },
390 )
391 }
392
393 pub fn remove(&mut self, contract_path: &Path, contract_name: &str) -> Option<T> {
395 self.0.iter_mut().filter(|(path, _)| path.as_path() == contract_path).find_map(
396 |(_file, contracts)| {
397 let mut artifact = None;
398 if let Some((c, mut artifacts)) = contracts.remove_entry(contract_name) {
399 if !artifacts.is_empty() {
400 artifact = Some(artifacts.remove(0).artifact);
401 }
402 if !artifacts.is_empty() {
403 contracts.insert(c, artifacts);
404 }
405 }
406 artifact
407 },
408 )
409 }
410
411 pub fn remove_first(&mut self, contract_name: &str) -> Option<T> {
416 self.0.iter_mut().find_map(|(_file, contracts)| {
417 let mut artifact = None;
418 if let Some((c, mut artifacts)) = contracts.remove_entry(contract_name) {
419 if !artifacts.is_empty() {
420 artifact = Some(artifacts.remove(0).artifact);
421 }
422 if !artifacts.is_empty() {
423 contracts.insert(c, artifacts);
424 }
425 }
426 artifact
427 })
428 }
429}
430
431pub trait Artifact {
433 fn into_inner(self) -> (Option<JsonAbi>, Option<Bytes>);
435
436 fn into_compact_contract(self) -> CompactContract;
438
439 fn into_contract_bytecode(self) -> CompactContractBytecode;
441
442 fn into_parts(self) -> (Option<JsonAbi>, Option<Bytes>, Option<Bytes>);
444
445 fn into_abi(self) -> Option<JsonAbi>
447 where
448 Self: Sized,
449 {
450 self.into_parts().0
451 }
452
453 fn into_bytecode_bytes(self) -> Option<Bytes>
455 where
456 Self: Sized,
457 {
458 self.into_parts().1
459 }
460 fn into_deployed_bytecode_bytes(self) -> Option<Bytes>
462 where
463 Self: Sized,
464 {
465 self.into_parts().2
466 }
467
468 fn try_into_parts(self) -> Result<(JsonAbi, Bytes, Bytes)>
470 where
471 Self: Sized,
472 {
473 let (abi, bytecode, deployed_bytecode) = self.into_parts();
474
475 Ok((
476 abi.ok_or_else(|| SolcError::msg("abi missing"))?,
477 bytecode.ok_or_else(|| SolcError::msg("bytecode missing"))?,
478 deployed_bytecode.ok_or_else(|| SolcError::msg("deployed bytecode missing"))?,
479 ))
480 }
481
482 fn get_contract_bytecode(&self) -> CompactContractBytecodeCow<'_>;
485
486 fn get_bytecode(&self) -> Option<Cow<'_, CompactBytecode>> {
488 self.get_contract_bytecode().bytecode
489 }
490
491 fn get_bytecode_object(&self) -> Option<Cow<'_, BytecodeObject>> {
493 let val = match self.get_bytecode()? {
494 Cow::Borrowed(b) => Cow::Borrowed(&b.object),
495 Cow::Owned(b) => Cow::Owned(b.object),
496 };
497 Some(val)
498 }
499
500 fn get_bytecode_bytes(&self) -> Option<Cow<'_, Bytes>> {
502 let val = match self.get_bytecode_object()? {
503 Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()?),
504 Cow::Owned(b) => Cow::Owned(b.into_bytes()?),
505 };
506 Some(val)
507 }
508
509 fn get_deployed_bytecode(&self) -> Option<Cow<'_, CompactDeployedBytecode>> {
511 self.get_contract_bytecode().deployed_bytecode
512 }
513
514 fn get_deployed_bytecode_object(&self) -> Option<Cow<'_, BytecodeObject>> {
516 let val = match self.get_deployed_bytecode()? {
517 Cow::Borrowed(b) => Cow::Borrowed(&b.bytecode.as_ref()?.object),
518 Cow::Owned(b) => Cow::Owned(b.bytecode?.object),
519 };
520 Some(val)
521 }
522
523 fn get_deployed_bytecode_bytes(&self) -> Option<Cow<'_, Bytes>> {
525 let val = match self.get_deployed_bytecode_object()? {
526 Cow::Borrowed(b) => Cow::Borrowed(b.as_bytes()?),
527 Cow::Owned(b) => Cow::Owned(b.into_bytes()?),
528 };
529 Some(val)
530 }
531
532 fn get_abi(&self) -> Option<Cow<'_, JsonAbi>> {
534 self.get_contract_bytecode().abi
535 }
536
537 fn get_source_map(&self) -> Option<std::result::Result<SourceMap, SyntaxError>> {
542 self.get_bytecode()?.source_map()
543 }
544
545 fn get_source_map_str(&self) -> Option<Cow<'_, str>> {
547 match self.get_bytecode()? {
548 Cow::Borrowed(code) => code.source_map.as_deref().map(Cow::Borrowed),
549 Cow::Owned(code) => code.source_map.map(Cow::Owned),
550 }
551 }
552
553 fn get_source_map_deployed(&self) -> Option<std::result::Result<SourceMap, SyntaxError>> {
558 self.get_deployed_bytecode()?.source_map()
559 }
560
561 fn get_source_map_deployed_str(&self) -> Option<Cow<'_, str>> {
563 match self.get_bytecode()? {
564 Cow::Borrowed(code) => code.source_map.as_deref().map(Cow::Borrowed),
565 Cow::Owned(code) => code.source_map.map(Cow::Owned),
566 }
567 }
568}
569
570impl<T> Artifact for T
571where
572 T: Into<CompactContractBytecode> + Into<CompactContract>,
573 for<'a> &'a T: Into<CompactContractBytecodeCow<'a>>,
574{
575 fn into_inner(self) -> (Option<JsonAbi>, Option<Bytes>) {
576 let artifact = self.into_compact_contract();
577 (artifact.abi, artifact.bin.and_then(|bin| bin.into_bytes()))
578 }
579
580 fn into_compact_contract(self) -> CompactContract {
581 self.into()
582 }
583
584 fn into_contract_bytecode(self) -> CompactContractBytecode {
585 self.into()
586 }
587
588 fn into_parts(self) -> (Option<JsonAbi>, Option<Bytes>, Option<Bytes>) {
589 self.into_compact_contract().into_parts()
590 }
591
592 fn get_contract_bytecode(&self) -> CompactContractBytecodeCow<'_> {
593 self.into()
594 }
595}
596
597pub trait ArtifactOutput {
610 type Artifact: Artifact + DeserializeOwned + Serialize + fmt::Debug + Send + Sync;
612 type CompilerContract: CompilerContract;
613
614 fn on_output<L>(
619 &self,
620 contracts: &VersionedContracts<Self::CompilerContract>,
621 sources: &VersionedSourceFiles,
622 layout: &ProjectPathsConfig<L>,
623 ctx: OutputContext<'_>,
624 primary_profiles: &HashMap<PathBuf, &str>,
625 ) -> Result<Artifacts<Self::Artifact>> {
626 let mut artifacts =
627 self.output_to_artifacts(contracts, sources, ctx, layout, primary_profiles);
628 fs::create_dir_all(&layout.artifacts).map_err(|err| {
629 error!(dir=?layout.artifacts, "Failed to create artifacts folder");
630 SolcIoError::new(err, &layout.artifacts)
631 })?;
632
633 artifacts.join_all(&layout.artifacts);
634 artifacts.write_all()?;
635
636 self.handle_artifacts(contracts, &artifacts)?;
637
638 Ok(artifacts)
639 }
640
641 fn handle_artifacts(
643 &self,
644 _contracts: &VersionedContracts<Self::CompilerContract>,
645 _artifacts: &Artifacts<Self::Artifact>,
646 ) -> Result<()> {
647 Ok(())
648 }
649
650 fn output_file_name(
653 name: &str,
654 version: &Version,
655 profile: &str,
656 with_version: bool,
657 with_profile: bool,
658 ) -> PathBuf {
659 let mut name = name.to_string();
660 if with_version {
661 name.push_str(&format!(".{}.{}.{}", version.major, version.minor, version.patch));
662 }
663 if with_profile {
664 name.push_str(&format!(".{profile}"));
665 }
666 name.push_str(".json");
667 name.into()
668 }
669
670 fn conflict_free_output_file(
683 already_taken: &HashSet<String>,
684 conflict: PathBuf,
685 contract_file: &Path,
686 artifacts_folder: &Path,
687 ) -> PathBuf {
688 let mut rel_candidate = conflict;
689 if let Ok(stripped) = rel_candidate.strip_prefix(artifacts_folder) {
690 rel_candidate = stripped.to_path_buf();
691 }
692 #[allow(clippy::redundant_clone)] let mut candidate = rel_candidate.clone();
694 let mut current_parent = contract_file.parent();
695
696 while let Some(parent_name) = current_parent.and_then(|f| f.file_name()) {
697 candidate = Path::new(parent_name).join(&candidate);
699 let out_path = artifacts_folder.join(&candidate);
700 if !already_taken.contains(&out_path.to_slash_lossy().to_lowercase()) {
701 trace!("found alternative output file={:?} for {:?}", out_path, contract_file);
702 return out_path;
703 }
704 current_parent = current_parent.and_then(|f| f.parent());
705 }
706
707 trace!("no conflict free output file found after traversing the file");
712
713 let mut num = 1;
714
715 loop {
716 let mut components = rel_candidate.components();
719 let first = components.next().expect("path not empty");
720 let name = first.as_os_str();
721 let mut numerated = OsString::with_capacity(name.len() + 2);
722 numerated.push(name);
723 numerated.push("_");
724 numerated.push(num.to_string());
725
726 let candidate: PathBuf = Some(numerated.as_os_str())
727 .into_iter()
728 .chain(components.map(|c| c.as_os_str()))
729 .collect();
730 if !already_taken.contains(&candidate.to_slash_lossy().to_lowercase()) {
731 trace!("found alternative output file={:?} for {:?}", candidate, contract_file);
732 return candidate;
733 }
734
735 num += 1;
736 }
737 }
738
739 fn output_file(
743 contract_file: &Path,
744 name: &str,
745 version: &Version,
746 profile: &str,
747 with_version: bool,
748 with_profile: bool,
749 ) -> PathBuf {
750 contract_file
751 .file_name()
752 .map(Path::new)
753 .map(|p| {
754 p.join(Self::output_file_name(name, version, profile, with_version, with_profile))
755 })
756 .unwrap_or_else(|| {
757 Self::output_file_name(name, version, profile, with_version, with_profile)
758 })
759 }
760
761 fn contract_name(file: &Path) -> Option<String> {
766 file.file_stem().and_then(|s| s.to_str().map(|s| s.to_string()))
767 }
768
769 fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {
777 utils::read_json_file(path)
778 }
779
780 fn read_cached_artifacts<T, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
784 where
785 I: IntoIterator<Item = T>,
786 T: Into<PathBuf>,
787 {
788 let mut artifacts = BTreeMap::default();
789 for path in files.into_iter() {
790 let path = path.into();
791 let artifact = Self::read_cached_artifact(&path)?;
792 artifacts.insert(path, artifact);
793 }
794 Ok(artifacts)
795 }
796
797 fn contract_to_artifact(
803 &self,
804 _file: &Path,
805 _name: &str,
806 contract: Self::CompilerContract,
807 source_file: Option<&SourceFile>,
808 ) -> Self::Artifact;
809
810 #[allow(clippy::too_many_arguments)]
813 fn get_artifact_path(
814 ctx: &OutputContext<'_>,
815 already_taken: &HashSet<String>,
816 file: &Path,
817 name: &str,
818 artifacts_folder: &Path,
819 version: &Version,
820 profile: &str,
821 with_version: bool,
822 with_profile: bool,
823 ) -> PathBuf {
824 if let Some(existing_artifact) = ctx.existing_artifact(file, name, version, profile) {
829 trace!("use existing artifact file {:?}", existing_artifact,);
830 existing_artifact.to_path_buf()
831 } else {
832 let path = Self::output_file(file, name, version, profile, with_version, with_profile);
833
834 let path = artifacts_folder.join(path);
835
836 if already_taken.contains(&path.to_slash_lossy().to_lowercase()) {
837 Self::conflict_free_output_file(already_taken, path, file, artifacts_folder)
839 } else {
840 path
841 }
842 }
843 }
844
845 fn output_to_artifacts<C>(
850 &self,
851 contracts: &VersionedContracts<Self::CompilerContract>,
852 sources: &VersionedSourceFiles,
853 ctx: OutputContext<'_>,
854 layout: &ProjectPathsConfig<C>,
855 primary_profiles: &HashMap<PathBuf, &str>,
856 ) -> Artifacts<Self::Artifact> {
857 let mut artifacts = ArtifactsMap::new();
858
859 let mut non_standalone_sources = HashSet::new();
861
862 let mut taken_paths_lowercase = ctx
864 .existing_artifacts
865 .values()
866 .flat_map(|artifacts| artifacts.values())
867 .flat_map(|artifacts| artifacts.values())
868 .flat_map(|artifacts| artifacts.values())
869 .map(|a| a.path.to_slash_lossy().to_lowercase())
870 .collect::<HashSet<_>>();
871
872 let mut files = contracts.keys().collect::<Vec<_>>();
873 files.sort_by(|&file1, &file2| {
875 (file1.components().count(), file1).cmp(&(file2.components().count(), file2))
876 });
877 for file in files {
878 for (name, versioned_contracts) in &contracts[file] {
879 let unique_versions =
880 versioned_contracts.iter().map(|c| &c.version).collect::<HashSet<_>>();
881 let unique_profiles =
882 versioned_contracts.iter().map(|c| &c.profile).collect::<HashSet<_>>();
883 let primary_profile = primary_profiles.get(file);
884
885 for contract in versioned_contracts {
886 non_standalone_sources.insert(file);
887
888 let source_file = sources.find_file_and_version(file, &contract.version);
890
891 let artifact_path = Self::get_artifact_path(
892 &ctx,
893 &taken_paths_lowercase,
894 file,
895 name,
896 layout.artifacts.as_path(),
897 &contract.version,
898 &contract.profile,
899 unique_versions.len() > 1,
900 unique_profiles.len() > 1
901 && primary_profile.is_none_or(|p| p != &contract.profile),
902 );
903
904 taken_paths_lowercase.insert(artifact_path.to_slash_lossy().to_lowercase());
905
906 trace!(
907 "use artifact file {:?} for contract file {} {}",
908 artifact_path,
909 file.display(),
910 contract.version
911 );
912
913 let artifact = self.contract_to_artifact(
914 file,
915 name,
916 contract.contract.clone(),
917 source_file,
918 );
919
920 let artifact = ArtifactFile {
921 artifact,
922 file: artifact_path,
923 version: contract.version.clone(),
924 build_id: contract.build_id.clone(),
925 profile: contract.profile.clone(),
926 };
927
928 artifacts
929 .entry(file.to_path_buf())
930 .or_default()
931 .entry(name.to_string())
932 .or_default()
933 .push(artifact);
934 }
935 }
936 }
937
938 for (file, sources) in sources.as_ref().iter() {
943 let unique_versions = sources.iter().map(|s| &s.version).collect::<HashSet<_>>();
944 let unique_profiles = sources.iter().map(|s| &s.profile).collect::<HashSet<_>>();
945 for source in sources {
946 if !non_standalone_sources.contains(file) {
947 if source.source_file.ast.is_none()
952 || source.source_file.contains_contract_definition()
953 {
954 continue;
955 }
956
957 if let Some(name) = Path::new(file).file_stem().and_then(|stem| stem.to_str()) {
959 if let Some(artifact) =
960 self.standalone_source_file_to_artifact(file, source)
961 {
962 let artifact_path = Self::get_artifact_path(
963 &ctx,
964 &taken_paths_lowercase,
965 file,
966 name,
967 &layout.artifacts,
968 &source.version,
969 &source.profile,
970 unique_versions.len() > 1,
971 unique_profiles.len() > 1,
972 );
973
974 taken_paths_lowercase
975 .insert(artifact_path.to_slash_lossy().to_lowercase());
976
977 artifacts
978 .entry(file.clone())
979 .or_default()
980 .entry(name.to_string())
981 .or_default()
982 .push(ArtifactFile {
983 artifact,
984 file: artifact_path,
985 version: source.version.clone(),
986 build_id: source.build_id.clone(),
987 profile: source.profile.clone(),
988 });
989 }
990 }
991 }
992 }
993 }
994
995 Artifacts(artifacts)
996 }
997
998 fn standalone_source_file_to_artifact(
1008 &self,
1009 _path: &Path,
1010 _file: &VersionedSourceFile,
1011 ) -> Option<Self::Artifact>;
1012
1013 fn is_dirty(&self, _artifact_file: &ArtifactFile<Self::Artifact>) -> Result<bool> {
1015 Ok(false)
1016 }
1017
1018 fn handle_cached_artifacts(&self, _artifacts: &Artifacts<Self::Artifact>) -> Result<()> {
1020 Ok(())
1021 }
1022}
1023
1024#[derive(Clone, Debug, Default)]
1026#[non_exhaustive]
1027pub struct OutputContext<'a> {
1028 pub existing_artifacts: BTreeMap<&'a Path, &'a CachedArtifacts>,
1040}
1041
1042impl<'a> OutputContext<'a> {
1045 pub fn new<S>(cache: &'a CompilerCache<S>) -> Self {
1047 let existing_artifacts = cache
1048 .files
1049 .iter()
1050 .map(|(file, entry)| (file.as_path(), &entry.artifacts))
1051 .collect::<BTreeMap<_, _>>();
1052
1053 Self { existing_artifacts }
1054 }
1055
1056 pub fn existing_artifact(
1061 &self,
1062 file: &Path,
1063 contract: &str,
1064 version: &Version,
1065 profile: &str,
1066 ) -> Option<&Path> {
1067 self.existing_artifacts
1068 .get(file)
1069 .and_then(|contracts| contracts.get(contract))
1070 .and_then(|versions| versions.get(version))
1071 .and_then(|profiles| profiles.get(profile))
1072 .map(|a| a.path.as_path())
1073 }
1074}
1075
1076#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1087pub struct MinimalCombinedArtifacts {
1088 _priv: (),
1089}
1090
1091impl ArtifactOutput for MinimalCombinedArtifacts {
1092 type Artifact = CompactContractBytecode;
1093 type CompilerContract = Contract;
1094
1095 fn contract_to_artifact(
1096 &self,
1097 _file: &Path,
1098 _name: &str,
1099 contract: Contract,
1100 _source_file: Option<&SourceFile>,
1101 ) -> Self::Artifact {
1102 Self::Artifact::from(contract)
1103 }
1104
1105 fn standalone_source_file_to_artifact(
1106 &self,
1107 _path: &Path,
1108 _file: &VersionedSourceFile,
1109 ) -> Option<Self::Artifact> {
1110 None
1111 }
1112}
1113
1114#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1117pub struct MinimalCombinedArtifactsHardhatFallback {
1118 _priv: (),
1119}
1120
1121impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
1122 type Artifact = CompactContractBytecode;
1123 type CompilerContract = Contract;
1124
1125 fn on_output<C>(
1126 &self,
1127 output: &VersionedContracts<Contract>,
1128 sources: &VersionedSourceFiles,
1129 layout: &ProjectPathsConfig<C>,
1130 ctx: OutputContext<'_>,
1131 primary_profiles: &HashMap<PathBuf, &str>,
1132 ) -> Result<Artifacts<Self::Artifact>> {
1133 MinimalCombinedArtifacts::default().on_output(
1134 output,
1135 sources,
1136 layout,
1137 ctx,
1138 primary_profiles,
1139 )
1140 }
1141
1142 fn read_cached_artifact(path: &Path) -> Result<Self::Artifact> {
1143 let content = fs::read_to_string(path).map_err(|err| SolcError::io(err, path))?;
1144 if let Ok(a) = serde_json::from_str(&content) {
1145 Ok(a)
1146 } else {
1147 error!("Failed to deserialize compact artifact");
1148 trace!("Fallback to hardhat artifact deserialization");
1149 let artifact = serde_json::from_str::<HardhatArtifact>(&content)?;
1150 trace!("successfully deserialized hardhat artifact");
1151 Ok(artifact.into_contract_bytecode())
1152 }
1153 }
1154
1155 fn contract_to_artifact(
1156 &self,
1157 file: &Path,
1158 name: &str,
1159 contract: Contract,
1160 source_file: Option<&SourceFile>,
1161 ) -> Self::Artifact {
1162 MinimalCombinedArtifacts::default().contract_to_artifact(file, name, contract, source_file)
1163 }
1164
1165 fn standalone_source_file_to_artifact(
1166 &self,
1167 path: &Path,
1168 file: &VersionedSourceFile,
1169 ) -> Option<Self::Artifact> {
1170 MinimalCombinedArtifacts::default().standalone_source_file_to_artifact(path, file)
1171 }
1172}
1173
1174#[cfg(test)]
1175mod tests {
1176 use super::*;
1177
1178 #[test]
1179 fn is_artifact() {
1180 fn assert_artifact<T: Artifact>() {}
1181
1182 assert_artifact::<CompactContractBytecode>();
1183 assert_artifact::<serde_json::Value>();
1184 }
1185
1186 #[test]
1187 fn can_find_alternate_paths() {
1188 let mut already_taken = HashSet::new();
1189
1190 let file = Path::new("v1/tokens/Greeter.sol");
1191 let conflict = PathBuf::from("out/Greeter.sol/Greeter.json");
1192 let artifacts_folder = Path::new("out");
1193
1194 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1195 &already_taken,
1196 conflict.clone(),
1197 file,
1198 artifacts_folder,
1199 );
1200 assert_eq!(alternative.to_slash_lossy(), "out/tokens/Greeter.sol/Greeter.json");
1201
1202 already_taken.insert("out/tokens/Greeter.sol/Greeter.json".to_lowercase());
1203 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1204 &already_taken,
1205 conflict.clone(),
1206 file,
1207 artifacts_folder,
1208 );
1209 assert_eq!(alternative.to_slash_lossy(), "out/v1/tokens/Greeter.sol/Greeter.json");
1210
1211 already_taken.insert("out/v1/tokens/Greeter.sol/Greeter.json".to_lowercase());
1212 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1213 &already_taken,
1214 conflict,
1215 file,
1216 artifacts_folder,
1217 );
1218 assert_eq!(alternative, PathBuf::from("Greeter.sol_1/Greeter.json"));
1219 }
1220
1221 #[test]
1222 fn can_find_alternate_path_conflict() {
1223 let mut already_taken = HashSet::new();
1224
1225 let file = "/Users/carter/dev/goldfinch/mono/packages/protocol/test/forge/mainnet/utils/BaseMainnetForkingTest.t.sol";
1226 let conflict = PathBuf::from("/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json");
1227 already_taken.insert("/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json".into());
1228
1229 let alternative = ConfigurableArtifacts::conflict_free_output_file(
1230 &already_taken,
1231 conflict,
1232 file.as_ref(),
1233 "/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts".as_ref(),
1234 );
1235
1236 assert_eq!(alternative.to_slash_lossy(), "/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts/utils/BaseMainnetForkingTest.t.sol/BaseMainnetForkingTest.json");
1237 }
1238
1239 fn assert_artifact<T: crate::Artifact>() {}
1240
1241 #[test]
1242 fn test() {
1243 assert_artifact::<CompactContractBytecode>();
1244 assert_artifact::<CompactContractBytecodeCow<'static>>();
1245 }
1246}