ethcontract_common/
artifact.rs

1//! Tools for loading artifacts that contain compiled contracts.
2//!
3//! Artifacts come in various shapes and sizes, but usually they
4//! are JSON files containing one or multiple compiled contracts
5//! as well as their deployment information.
6//!
7//! This module provides trait [`Artifact`] that encapsulates different
8//! artifact models. It also provides tools to load artifacts from different
9//! sources, and parse them using different formats.
10
11use crate::contract::{Documentation, Interface, Network};
12use crate::{Abi, Bytecode, Contract};
13use std::collections::hash_map::Entry;
14use std::collections::HashMap;
15use std::ops::Deref;
16use std::sync::Arc;
17
18pub mod hardhat;
19pub mod truffle;
20
21/// An entity that contains compiled contracts.
22pub struct Artifact {
23    origin: String,
24    contracts: HashMap<String, Contract>,
25}
26
27impl Artifact {
28    /// Creates a new empty artifact.
29    pub fn new() -> Self {
30        Artifact {
31            origin: "<unknown>".to_string(),
32            contracts: HashMap::new(),
33        }
34    }
35
36    /// Creates a new artifact with an origin information.
37    pub fn with_origin(origin: impl Into<String>) -> Self {
38        Artifact {
39            origin: origin.into(),
40            contracts: HashMap::new(),
41        }
42    }
43
44    /// Provides description of where this artifact comes from.
45    ///
46    /// This function is used when a human-readable reference to the artifact
47    /// is required. It could be anything: path to a json file, url, etc.
48    pub fn origin(&self) -> &str {
49        &self.origin
50    }
51
52    /// Sets new origin for the artifact.
53    ///
54    /// Artifact loaders will set origin to something meaningful in most cases,
55    /// so this function should not be used often. There are cases when
56    /// it is required, though.
57    pub fn set_origin(&mut self, origin: impl Into<String>) {
58        self.origin = origin.into();
59    }
60
61    /// Gets number of contracts contained in this artifact.
62    pub fn len(&self) -> usize {
63        self.contracts.len()
64    }
65
66    /// Returns `true` if this artifact contains no contracts.
67    pub fn is_empty(&self) -> bool {
68        self.contracts.is_empty()
69    }
70
71    /// Returns `true` if this artifact has a contract with the given name.
72    pub fn contains(&self, name: &str) -> bool {
73        self.contracts.contains_key(name)
74    }
75
76    /// Looks up contract by its name and returns a reference to it.
77    ///
78    /// Some artifact formats allow exporting a single unnamed contract.
79    /// In this case, the contract will have an empty string as its name.
80    pub fn get(&self, name: &str) -> Option<&Contract> {
81        self.contracts.get(name)
82    }
83
84    /// Looks up contract by its name and returns a handle that allows
85    /// safely mutating it.
86    ///
87    /// The returned handle does not allow renaming contract. For that,
88    /// you'll need to remove it and add again.
89    pub fn get_mut(&mut self, name: &str) -> Option<ContractMut> {
90        self.contracts.get_mut(name).map(ContractMut)
91    }
92
93    /// Inserts a new contract to the artifact.
94    ///
95    /// If contract with this name already exists, replaces it
96    /// and returns the old contract.
97    pub fn insert(&mut self, contract: Contract) -> InsertResult {
98        match self.contracts.entry(contract.name.clone()) {
99            Entry::Occupied(mut o) => {
100                let old_contract = o.insert(contract);
101                InsertResult {
102                    inserted_contract: ContractMut(o.into_mut()),
103                    old_contract: Some(old_contract),
104                }
105            }
106            Entry::Vacant(v) => InsertResult {
107                inserted_contract: ContractMut(v.insert(contract)),
108                old_contract: None,
109            },
110        }
111    }
112
113    /// Removes contract from the artifact.
114    ///
115    /// Returns removed contract or [`None`] if contract with the given name
116    /// wasn't found.
117    pub fn remove(&mut self, name: &str) -> Option<Contract> {
118        self.contracts.remove(name)
119    }
120
121    /// Creates an iterator that yields the artifact's contracts.
122    pub fn iter(&self) -> impl Iterator<Item = &Contract> + '_ {
123        self.contracts.values()
124    }
125
126    /// Takes all contracts from the artifact, leaving it empty,
127    /// and returns an iterator over the taken contracts.
128    pub fn drain(&mut self) -> impl Iterator<Item = Contract> + '_ {
129        self.contracts.drain().map(|(_, contract)| contract)
130    }
131}
132
133impl Default for Artifact {
134    fn default() -> Self {
135        Artifact::new()
136    }
137}
138
139/// Result of inserting a nre contract into an artifact.
140pub struct InsertResult<'a> {
141    /// Reference to the newly inserted contract.
142    pub inserted_contract: ContractMut<'a>,
143
144    /// If insert operation replaced an old contract, it will appear here.
145    pub old_contract: Option<Contract>,
146}
147
148/// A wrapper that allows mutating contract
149/// but doesn't allow changing its name.
150pub struct ContractMut<'a>(&'a mut Contract);
151
152impl ContractMut<'_> {
153    /// Returns mutable reference to contract's abi.
154    pub fn abi_mut(&mut self) -> &mut Abi {
155        &mut Arc::make_mut(&mut self.0.interface).abi
156    }
157
158    /// Returns mutable reference to contract's bytecode.
159    pub fn bytecode_mut(&mut self) -> &mut Bytecode {
160        &mut self.0.bytecode
161    }
162
163    /// Returns mutable reference to contract's bytecode.
164    pub fn deployed_bytecode_mut(&mut self) -> &mut Bytecode {
165        &mut self.0.deployed_bytecode
166    }
167
168    /// Returns mutable reference to contract's networks.
169    pub fn networks_mut(&mut self) -> &mut HashMap<String, Network> {
170        &mut self.0.networks
171    }
172
173    /// Returns mutable reference to contract's devdoc.
174    pub fn devdoc_mut(&mut self) -> &mut Documentation {
175        &mut self.0.devdoc
176    }
177
178    /// Returns mutable reference to contract's userdoc.
179    pub fn userdoc_mut(&mut self) -> &mut Documentation {
180        &mut self.0.userdoc
181    }
182}
183
184impl Deref for ContractMut<'_> {
185    type Target = Contract;
186
187    fn deref(&self) -> &Self::Target {
188        self.0
189    }
190}
191
192impl Drop for ContractMut<'_> {
193    fn drop(&mut self) {
194        // The ABI might have gotten mutated while this guard was alive.
195        // Since we compute pre-compute and cache a few values based on the ABI
196        // as a performance optimization we need to recompute those cached values
197        // with the new ABI once the user is done updating the mutable contract.
198        let abi = self.0.interface.abi.clone();
199        let interface = Interface::from(abi);
200        *Arc::make_mut(&mut self.0.interface) = interface;
201    }
202}
203
204#[cfg(test)]
205mod test {
206    use super::*;
207
208    fn make_contract(name: &str) -> Contract {
209        let mut contract = Contract::empty();
210        contract.name = name.to_string();
211        contract
212    }
213
214    #[test]
215    fn insert() {
216        let mut artifact = Artifact::new();
217
218        assert_eq!(artifact.len(), 0);
219
220        {
221            let insert_res = artifact.insert(make_contract("C1"));
222
223            assert_eq!(insert_res.inserted_contract.name, "C1");
224            assert!(insert_res.old_contract.is_none());
225        }
226
227        assert_eq!(artifact.len(), 1);
228        assert!(artifact.contains("C1"));
229
230        {
231            let insert_res = artifact.insert(make_contract("C2"));
232
233            assert_eq!(insert_res.inserted_contract.name, "C2");
234            assert!(insert_res.old_contract.is_none());
235        }
236
237        assert_eq!(artifact.len(), 2);
238        assert!(artifact.contains("C2"));
239
240        {
241            let insert_res = artifact.insert(make_contract("C1"));
242
243            assert_eq!(insert_res.inserted_contract.name, "C1");
244            assert!(insert_res.old_contract.is_some());
245        }
246
247        assert_eq!(artifact.len(), 2);
248    }
249
250    #[test]
251    fn remove() {
252        let mut artifact = Artifact::new();
253
254        artifact.insert(make_contract("C1"));
255        artifact.insert(make_contract("C2"));
256
257        assert_eq!(artifact.len(), 2);
258        assert!(artifact.contains("C1"));
259        assert!(artifact.contains("C2"));
260
261        let c0 = artifact.remove("C0");
262        assert!(c0.is_none());
263
264        assert_eq!(artifact.len(), 2);
265        assert!(artifact.contains("C1"));
266        assert!(artifact.contains("C2"));
267
268        let c1 = artifact.remove("C1");
269
270        assert!(c1.is_some());
271        assert_eq!(c1.unwrap().name, "C1");
272
273        assert_eq!(artifact.len(), 1);
274        assert!(!artifact.contains("C1"));
275        assert!(artifact.contains("C2"));
276    }
277}