ethers_solc/compile/output/
contracts.rs

1use crate::{
2    artifacts::{
3        contract::{CompactContractRef, Contract},
4        CompactContractBytecode, FileToContractsMap,
5    },
6    files::{MappedArtifactFile, MappedArtifactFiles, MappedContract},
7    ArtifactId, ArtifactOutput, OutputContext,
8};
9use semver::Version;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use std::{
12    collections::BTreeMap,
13    ops::{Deref, DerefMut},
14    path::Path,
15};
16use tracing::trace;
17
18/// file -> [(contract name  -> Contract + solc version)]
19#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
20#[serde(transparent)]
21pub struct VersionedContracts(pub FileToContractsMap<Vec<VersionedContract>>);
22
23impl VersionedContracts {
24    /// Converts all `\\` separators in _all_ paths to `/`
25    pub fn slash_paths(&mut self) {
26        #[cfg(windows)]
27        {
28            use path_slash::PathExt;
29            self.0 = std::mem::take(&mut self.0)
30                .into_iter()
31                .map(|(path, files)| (Path::new(&path).to_slash_lossy().to_string(), files))
32                .collect()
33        }
34    }
35    pub fn is_empty(&self) -> bool {
36        self.0.is_empty()
37    }
38
39    pub fn len(&self) -> usize {
40        self.0.len()
41    }
42
43    /// Returns an iterator over all files
44    pub fn files(&self) -> impl Iterator<Item = &String> + '_ {
45        self.0.keys()
46    }
47
48    /// Returns all the artifact files mapped with their contracts
49    ///
50    /// This will compute the appropriate output file paths but will _not_ write them.
51    /// The `ctx` is used to avoid possible conflicts
52    pub(crate) fn artifact_files<T: ArtifactOutput + ?Sized>(
53        &self,
54        ctx: &OutputContext,
55    ) -> MappedArtifactFiles {
56        let mut output_files = MappedArtifactFiles::with_capacity(self.len());
57        for (file, contracts) in self.iter() {
58            for (name, versioned_contracts) in contracts {
59                for contract in versioned_contracts {
60                    // if an artifact for the contract already exists (from a previous compile job)
61                    // we reuse the path, this will make sure that even if there are conflicting
62                    // files (files for witch `T::output_file()` would return the same path) we use
63                    // consistent output paths
64                    let artifact_path = if let Some(existing_artifact) =
65                        ctx.existing_artifact(file, name, &contract.version).cloned()
66                    {
67                        trace!("use existing artifact file {:?}", existing_artifact,);
68                        existing_artifact
69                    } else if versioned_contracts.len() > 1 {
70                        T::output_file_versioned(file, name, &contract.version)
71                    } else {
72                        T::output_file(file, name)
73                    };
74
75                    trace!(
76                        "use artifact file {:?} for contract file {} {}",
77                        artifact_path,
78                        file,
79                        contract.version
80                    );
81                    let artifact = MappedArtifactFile::new(&artifact_path);
82                    let contract = MappedContract {
83                        file: file.as_str(),
84                        name: name.as_str(),
85                        contract,
86                        artifact_path,
87                    };
88                    output_files.entry(artifact).or_default().push(contract);
89                }
90            }
91        }
92
93        output_files
94    }
95
96    /// Finds the _first_ contract with the given name
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use ethers_solc::Project;
102    /// use ethers_solc::artifacts::*;
103    /// # fn demo(project: Project) {
104    /// let output = project.compile().unwrap().output();
105    /// let contract = output.find_first("Greeter").unwrap();
106    /// # }
107    /// ```
108    pub fn find_first(&self, contract: impl AsRef<str>) -> Option<CompactContractRef> {
109        let contract_name = contract.as_ref();
110        self.contracts().find_map(|(name, contract)| {
111            (name == contract_name).then(|| CompactContractRef::from(contract))
112        })
113    }
114
115    /// Finds the contract with matching path and name
116    ///
117    /// # Example
118    ///
119    /// ```
120    /// use ethers_solc::Project;
121    /// use ethers_solc::artifacts::*;
122    /// # fn demo(project: Project) {
123    /// let output = project.compile().unwrap().output();
124    /// let contract = output.contracts.find("src/Greeter.sol", "Greeter").unwrap();
125    /// # }
126    /// ```
127    pub fn find(
128        &self,
129        path: impl AsRef<str>,
130        contract: impl AsRef<str>,
131    ) -> Option<CompactContractRef> {
132        let contract_path = path.as_ref();
133        let contract_name = contract.as_ref();
134        self.contracts_with_files().find_map(|(path, name, contract)| {
135            (path == contract_path && name == contract_name)
136                .then(|| CompactContractRef::from(contract))
137        })
138    }
139
140    /// Removes the _first_ contract with the given name from the set
141    ///
142    /// # Example
143    ///
144    /// ```
145    /// use ethers_solc::Project;
146    /// use ethers_solc::artifacts::*;
147    /// # fn demo(project: Project) {
148    /// let (_, mut contracts) = project.compile().unwrap().output().split();
149    /// let contract = contracts.remove_first("Greeter").unwrap();
150    /// # }
151    /// ```
152    pub fn remove_first(&mut self, contract: impl AsRef<str>) -> Option<Contract> {
153        let contract_name = contract.as_ref();
154        self.0.values_mut().find_map(|all_contracts| {
155            let mut contract = None;
156            if let Some((c, mut contracts)) = all_contracts.remove_entry(contract_name) {
157                if !contracts.is_empty() {
158                    contract = Some(contracts.remove(0).contract);
159                }
160                if !contracts.is_empty() {
161                    all_contracts.insert(c, contracts);
162                }
163            }
164            contract
165        })
166    }
167
168    ///  Removes the contract with matching path and name
169    ///
170    /// # Example
171    ///
172    /// ```
173    /// use ethers_solc::Project;
174    /// use ethers_solc::artifacts::*;
175    /// # fn demo(project: Project) {
176    /// let (_, mut contracts) = project.compile().unwrap().output().split();
177    /// let contract = contracts.remove("src/Greeter.sol", "Greeter").unwrap();
178    /// # }
179    /// ```
180    pub fn remove(&mut self, path: impl AsRef<str>, contract: impl AsRef<str>) -> Option<Contract> {
181        let contract_name = contract.as_ref();
182        let (key, mut all_contracts) = self.0.remove_entry(path.as_ref())?;
183        let mut contract = None;
184        if let Some((c, mut contracts)) = all_contracts.remove_entry(contract_name) {
185            if !contracts.is_empty() {
186                contract = Some(contracts.remove(0).contract);
187            }
188            if !contracts.is_empty() {
189                all_contracts.insert(c, contracts);
190            }
191        }
192
193        if !all_contracts.is_empty() {
194            self.0.insert(key, all_contracts);
195        }
196        contract
197    }
198
199    /// Given the contract file's path and the contract's name, tries to return the contract's
200    /// bytecode, runtime bytecode, and abi
201    pub fn get(
202        &self,
203        path: impl AsRef<str>,
204        contract: impl AsRef<str>,
205    ) -> Option<CompactContractRef> {
206        let contract = contract.as_ref();
207        self.0
208            .get(path.as_ref())
209            .and_then(|contracts| {
210                contracts.get(contract).and_then(|c| c.first().map(|c| &c.contract))
211            })
212            .map(CompactContractRef::from)
213    }
214
215    /// Iterate over all contracts and their names
216    pub fn contracts(&self) -> impl Iterator<Item = (&String, &Contract)> {
217        self.0
218            .values()
219            .flat_map(|c| c.iter().flat_map(|(name, c)| c.iter().map(move |c| (name, &c.contract))))
220    }
221
222    /// Returns an iterator over (`file`, `name`, `Contract`)
223    pub fn contracts_with_files(&self) -> impl Iterator<Item = (&String, &String, &Contract)> {
224        self.0.iter().flat_map(|(file, contracts)| {
225            contracts
226                .iter()
227                .flat_map(move |(name, c)| c.iter().map(move |c| (file, name, &c.contract)))
228        })
229    }
230
231    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
232    pub fn contracts_with_files_and_version(
233        &self,
234    ) -> impl Iterator<Item = (&String, &String, &Contract, &Version)> {
235        self.0.iter().flat_map(|(file, contracts)| {
236            contracts.iter().flat_map(move |(name, c)| {
237                c.iter().map(move |c| (file, name, &c.contract, &c.version))
238            })
239        })
240    }
241
242    /// Returns an iterator over all contracts and their source names.
243    ///
244    /// ```
245    /// use std::collections::BTreeMap;
246    /// use ethers_solc::{ artifacts::*, Artifact };
247    /// use ethers_solc::artifacts::contract::CompactContractSome;
248    /// # fn demo(contracts: OutputContracts) {
249    /// let contracts: BTreeMap<String, CompactContractSome> = contracts
250    ///     .into_contracts()
251    ///     .map(|(k, c)| (k, c.into_compact_contract().unwrap()))
252    ///     .collect();
253    /// # }
254    /// ```
255    pub fn into_contracts(self) -> impl Iterator<Item = (String, Contract)> {
256        self.0.into_values().flat_map(|c| {
257            c.into_iter()
258                .flat_map(|(name, c)| c.into_iter().map(move |c| (name.clone(), c.contract)))
259        })
260    }
261
262    /// Returns an iterator over (`file`, `name`, `Contract`)
263    pub fn into_contracts_with_files(self) -> impl Iterator<Item = (String, String, Contract)> {
264        self.0.into_iter().flat_map(|(file, contracts)| {
265            contracts.into_iter().flat_map(move |(name, c)| {
266                let file = file.clone();
267                c.into_iter().map(move |c| (file.clone(), name.clone(), c.contract))
268            })
269        })
270    }
271
272    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
273    pub fn into_contracts_with_files_and_version(
274        self,
275    ) -> impl Iterator<Item = (String, String, Contract, Version)> {
276        self.0.into_iter().flat_map(|(file, contracts)| {
277            contracts.into_iter().flat_map(move |(name, c)| {
278                let file = file.clone();
279                c.into_iter().map(move |c| (file.clone(), name.clone(), c.contract, c.version))
280            })
281        })
282    }
283
284    /// Sets the contract's file paths to `root` adjoined to `self.file`.
285    pub fn join_all(&mut self, root: impl AsRef<Path>) -> &mut Self {
286        let root = root.as_ref();
287        self.0 = std::mem::take(&mut self.0)
288            .into_iter()
289            .map(|(contract_path, contracts)| {
290                (format!("{}", root.join(contract_path).display()), contracts)
291            })
292            .collect();
293        self
294    }
295
296    /// Removes `base` from all contract paths
297    pub fn strip_prefix_all(&mut self, base: impl AsRef<Path>) -> &mut Self {
298        let base = base.as_ref();
299        self.0 = std::mem::take(&mut self.0)
300            .into_iter()
301            .map(|(contract_path, contracts)| {
302                let p = Path::new(&contract_path);
303                (
304                    p.strip_prefix(base)
305                        .map(|p| p.to_string_lossy().to_string())
306                        .unwrap_or(contract_path),
307                    contracts,
308                )
309            })
310            .collect();
311        self
312    }
313}
314
315impl AsRef<FileToContractsMap<Vec<VersionedContract>>> for VersionedContracts {
316    fn as_ref(&self) -> &FileToContractsMap<Vec<VersionedContract>> {
317        &self.0
318    }
319}
320
321impl AsMut<FileToContractsMap<Vec<VersionedContract>>> for VersionedContracts {
322    fn as_mut(&mut self) -> &mut FileToContractsMap<Vec<VersionedContract>> {
323        &mut self.0
324    }
325}
326
327impl Deref for VersionedContracts {
328    type Target = FileToContractsMap<Vec<VersionedContract>>;
329
330    fn deref(&self) -> &Self::Target {
331        &self.0
332    }
333}
334
335impl IntoIterator for VersionedContracts {
336    type Item = (String, BTreeMap<String, Vec<VersionedContract>>);
337    type IntoIter =
338        std::collections::btree_map::IntoIter<String, BTreeMap<String, Vec<VersionedContract>>>;
339
340    fn into_iter(self) -> Self::IntoIter {
341        self.0.into_iter()
342    }
343}
344
345/// A contract and the compiler version used to compile it
346#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
347pub struct VersionedContract {
348    pub contract: Contract,
349    pub version: Version,
350}
351
352/// A mapping of `ArtifactId` and their `CompactContractBytecode`
353#[derive(Debug, Clone, PartialEq, Eq, Default)]
354pub struct ArtifactContracts<T = CompactContractBytecode>(pub BTreeMap<ArtifactId, T>);
355
356impl<T: Serialize> Serialize for ArtifactContracts<T> {
357    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
358    where
359        S: Serializer,
360    {
361        self.0.serialize(serializer)
362    }
363}
364
365impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArtifactContracts<T> {
366    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
367    where
368        D: Deserializer<'de>,
369    {
370        Ok(Self(BTreeMap::<_, _>::deserialize(deserializer)?))
371    }
372}
373
374impl<T> Deref for ArtifactContracts<T> {
375    type Target = BTreeMap<ArtifactId, T>;
376
377    fn deref(&self) -> &Self::Target {
378        &self.0
379    }
380}
381
382impl<T> DerefMut for ArtifactContracts<T> {
383    fn deref_mut(&mut self) -> &mut Self::Target {
384        &mut self.0
385    }
386}
387
388impl<V, C: Into<V>> FromIterator<(ArtifactId, C)> for ArtifactContracts<V> {
389    fn from_iter<T: IntoIterator<Item = (ArtifactId, C)>>(iter: T) -> Self {
390        Self(iter.into_iter().map(|(k, v)| (k, v.into())).collect())
391    }
392}
393
394impl<T> IntoIterator for ArtifactContracts<T> {
395    type Item = (ArtifactId, T);
396    type IntoIter = std::collections::btree_map::IntoIter<ArtifactId, T>;
397
398    fn into_iter(self) -> Self::IntoIter {
399        self.0.into_iter()
400    }
401}