ethcontract_common/artifact/
hardhat.rs

1//! Implements artifact format generated by [hardhat-deploy] plugin.
2//!
3//! There are three distinct artifact formats.
4//!
5//! First is called "hardhat export", it is a JSON file that contains
6//! information about a single network and all contracts deployed on it.
7//! It can be generated with `hardhat export` command.
8//!
9//! Second is called "hardhat multi-export", it contains information about
10//! multiple networks, for each network it contains information about
11//! all contracts deployed on it. It can be generated with
12//! `hardhat export --export-all` command.
13//!
14//! Third is hardhat's `deployments` directory. It contains more details about
15//! contracts than the previous two formats. Specifically, it has info about
16//! deployed bytecode, deployment transaction receipt, documentation for
17//! contract methods, and some other things. Given that it is a directory,
18//! there are obvious issues with loading it over network. For this reason,
19//! we don't recommend this export format for public libraries that export
20//! contracts.
21//!
22//! All three formats are supported by [`HardHatLoader`], see its documentation
23//! for info and limitations.
24//!
25//! [hardhat-deploy]: https://github.com/wighawag/hardhat-deploy
26
27use crate::artifact::Artifact;
28use crate::contract::Network;
29use crate::errors::ArtifactError;
30use crate::{Address, Contract, DeploymentInformation, TransactionHash};
31use serde::Deserialize;
32use serde_json::{from_reader, from_slice, from_str, from_value, Value};
33use std::collections::HashMap;
34use std::fs::File;
35use std::io::{BufReader, Read};
36use std::path::Path;
37
38/// Loads hardhat artifacts generated via `--export` and `--export-all`.
39///
40/// # Limitations
41///
42/// In hardhat, a contract could have different ABIs on different networks.
43/// This could happen when deploying test versions of contracts.
44/// Ethcontract does not support this. Parsing such artifact will result
45/// in an error. You'll have to rename contracts, or filter out networks
46/// with [`networks_allow_list`].
47///
48/// Another limitation is that hardhat allows having multiple networks
49/// with the same chain ID. For example, you can have `rinkeby`
50/// and `rinkeby-testing`. Both have chain ID of `4`, but contract addresses
51/// and ABIs can be different. Ethcontract does not support this, so you'll
52/// have to filter such networks. See [#545] for more info.
53///
54/// [#545]: https://github.com/gnosis/ethcontract-rs/issues/545.
55#[must_use = "hardhat loaders do nothing unless you load them"]
56pub struct HardHatLoader {
57    /// Override for artifact's origin. If `None`, origin
58    /// will be derived automatically.
59    pub origin: Option<String>,
60
61    /// List of allowed network names and chain IDs.
62    ///
63    /// When loading a contract, networks with names that aren't found
64    /// in this list will be completely ignored. Contracts from these networks
65    /// will not be loaded. You can use this mechanism to bypass
66    /// the requirement that a contract must have the same ABI on all networks.
67    ///
68    /// Empty list means that all networks are allowed.
69    pub networks_allow_list: Vec<NetworkEntry>,
70
71    /// List of denied network names and chain IDs.
72    ///
73    /// When loading a contract, networks with names that are found
74    /// in this list will be completely ignored.
75    ///
76    /// Empty list means that no networks are denied.
77    ///
78    /// Deny list takes precedence over allow list. That is, if network
79    /// appears in both, it will be denied.
80    pub networks_deny_list: Vec<NetworkEntry>,
81
82    /// List of allowed contract names.
83    ///
84    /// When loading artifact, loader will only load contracts if their names
85    /// are present in this list.
86    ///
87    /// Empty list means that all contracts are allowed.
88    pub contracts_allow_list: Vec<String>,
89
90    /// List of denied contract names.
91    ///
92    /// When loading artifact, loader will not load contracts if their names
93    /// are present in this list.
94    ///
95    /// Empty list means that no contracts are denied.
96    ///
97    /// Deny list takes precedence over allow list. That is, if contract
98    /// appears in both, it will be denied.
99    pub contracts_deny_list: Vec<String>,
100}
101
102impl HardHatLoader {
103    /// Creates a new hardhat loader.
104    pub fn new() -> Self {
105        HardHatLoader {
106            origin: None,
107            networks_deny_list: Vec::new(),
108            networks_allow_list: Vec::new(),
109            contracts_allow_list: Vec::new(),
110            contracts_deny_list: Vec::new(),
111        }
112    }
113
114    /// Creates a new hardhat loader and sets an override for artifact's origins.z
115    pub fn with_origin(origin: impl Into<String>) -> Self {
116        HardHatLoader {
117            origin: Some(origin.into()),
118            networks_deny_list: Vec::new(),
119            networks_allow_list: Vec::new(),
120            contracts_allow_list: Vec::new(),
121            contracts_deny_list: Vec::new(),
122        }
123    }
124
125    /// Sets new override for artifact's origin. See [`origin`] for more info.
126    ///
127    /// [`origin`]: #structfield.origin
128    pub fn origin(mut self, origin: impl Into<String>) -> Self {
129        self.origin = Some(origin.into());
130        self
131    }
132
133    /// Adds chain id to the list of [`allowed networks`].
134    ///
135    /// [`allowed networks`]: #structfield.networks_allow_list
136    pub fn allow_network_by_chain_id(mut self, network: impl Into<String>) -> Self {
137        self.networks_allow_list
138            .push(NetworkEntry::ByChainId(network.into()));
139        self
140    }
141
142    /// Adds network name to the list of [`allowed networks`].
143    ///
144    /// [`allowed networks`]: #structfield.networks_allow_list
145    pub fn allow_network_by_name(mut self, network: impl Into<String>) -> Self {
146        self.networks_allow_list
147            .push(NetworkEntry::ByName(network.into()));
148        self
149    }
150
151    /// Adds chain id to the list of [`denied networks`].
152    ///
153    /// [`denied networks`]: #structfield.networks_deny_list
154    pub fn deny_network_by_chain_id(mut self, network: impl Into<String>) -> Self {
155        self.networks_deny_list
156            .push(NetworkEntry::ByChainId(network.into()));
157        self
158    }
159
160    /// Adds network name to the list of [`denied networks`].
161    ///
162    /// [`denied networks`]: #structfield.networks_deny_list
163    pub fn deny_network_by_name(mut self, network: impl Into<String>) -> Self {
164        self.networks_deny_list
165            .push(NetworkEntry::ByName(network.into()));
166        self
167    }
168
169    /// Adds contract name to the list of [`allowed contracts`].
170    ///
171    /// [`allowed contracts`]: #structfield.contracts_allow_list
172    pub fn allow_contract(mut self, contract: impl Into<String>) -> Self {
173        self.contracts_allow_list.push(contract.into());
174        self
175    }
176
177    /// Adds contract name to the list of [`denied contracts`].
178    ///
179    /// [`denied contracts`]: #structfield.contracts_deny_list
180    pub fn deny_contract(mut self, contract: impl Into<String>) -> Self {
181        self.contracts_deny_list.push(contract.into());
182        self
183    }
184
185    /// Loads an artifact from a JSON value.
186    pub fn load_from_reader(&self, f: Format, v: impl Read) -> Result<Artifact, ArtifactError> {
187        self.load_artifact(f, "<unknown>", v, from_reader, from_reader)
188    }
189
190    /// Loads an artifact from bytes of JSON text.
191    pub fn load_from_slice(&self, f: Format, v: &[u8]) -> Result<Artifact, ArtifactError> {
192        self.load_artifact(f, "<unknown>", v, from_slice, from_slice)
193    }
194
195    /// Loads an artifact from string of JSON text.
196    pub fn load_from_str(&self, f: Format, v: &str) -> Result<Artifact, ArtifactError> {
197        self.load_artifact(f, "<unknown>", v, from_str, from_str)
198    }
199
200    /// Loads an artifact from a loaded JSON value.
201    pub fn load_from_value(&self, f: Format, v: Value) -> Result<Artifact, ArtifactError> {
202        self.load_artifact(f, "<unknown>", v, from_value, from_value)
203    }
204
205    /// Loads an artifact from disk.
206    pub fn load_from_file(
207        &self,
208        f: Format,
209        p: impl AsRef<Path>,
210    ) -> Result<Artifact, ArtifactError> {
211        let path = p.as_ref();
212        let file = File::open(path)?;
213        let reader = BufReader::new(file);
214        self.load_artifact(f, path.display(), reader, from_reader, from_reader)
215    }
216
217    /// Loads an artifact from `deployments` directory.
218    pub fn load_from_directory(&self, p: impl AsRef<Path>) -> Result<Artifact, ArtifactError> {
219        self._load_from_directory(p.as_ref())
220    }
221
222    /// Helper for `load_from_directory`. We use this helper function to avoid
223    /// making a big chunk of code generic over `AsRef<Path>`.
224    ///
225    /// # Implementation note
226    ///
227    /// Layout of the `deployments` directory looks like this:
228    ///
229    /// ```text
230    /// deployments
231    ///  |
232    ///  +-- main
233    ///  |    |
234    ///  |    +-- .chainId
235    ///  |    |
236    ///  |    +-- Contract1.json
237    ///  |    |
238    ///  |    +-- Contract2.json
239    ///  |    |
240    ///  |    ...
241    ///  |
242    ///  +-- rinkeby
243    ///  |    |
244    ///  |    +-- .chainId
245    ///  |    |
246    ///  |    +-- Contract1.json
247    ///  |    |
248    ///  |    +-- Contract2.json
249    ///  |    |
250    ///  |    ...
251    ///  |
252    ///  ...
253    ///  ```
254    ///
255    /// There's a directory for each network. Within network's directory,
256    /// there's a `.chainId` file containing chain identifier encoded
257    /// as plain text. Next to `.chainId` file, there are JSON files for each
258    /// contract deployed to this network.
259    fn _load_from_directory(&self, p: &Path) -> Result<Artifact, ArtifactError> {
260        let mut artifact = Artifact::with_origin(p.display().to_string());
261
262        let mut chain_id_buf = String::new();
263
264        for chain_entry in p.read_dir()? {
265            let chain_entry = chain_entry?;
266
267            let chain_path = chain_entry.path();
268            if !chain_path.is_dir() {
269                continue;
270            }
271
272            let chain_id_file = chain_path.join(".chainId");
273            if !chain_id_file.exists() {
274                continue;
275            }
276
277            chain_id_buf.clear();
278            File::open(chain_id_file)?.read_to_string(&mut chain_id_buf)?;
279            let chain_id = chain_id_buf.trim().to_string();
280
281            let chain_name = chain_path
282                .file_name()
283                .ok_or_else(|| {
284                    std::io::Error::new(
285                        std::io::ErrorKind::Other,
286                        format!("unable to get directory name for path {:?}", chain_path),
287                    )
288                })?
289                .to_string_lossy();
290
291            if !self.network_allowed(&chain_id, &chain_name) {
292                continue;
293            }
294
295            for contract_entry in chain_path.read_dir()? {
296                let contract_entry = contract_entry?;
297
298                let contract_path = contract_entry.path();
299                if !contract_path.is_file() {
300                    continue;
301                }
302
303                let mut contract_name = contract_path
304                    .file_name()
305                    .ok_or_else(|| {
306                        std::io::Error::new(
307                            std::io::ErrorKind::Other,
308                            format!("unable to get file name for path {:?}", contract_path),
309                        )
310                    })?
311                    .to_string_lossy()
312                    .into_owned();
313
314                if !contract_name.ends_with(".json") {
315                    continue;
316                }
317
318                contract_name.truncate(contract_name.len() - ".json".len());
319
320                if !self.contract_allowed(&contract_name) {
321                    continue;
322                }
323
324                let HardHatContract {
325                    address,
326                    transaction_hash,
327                    mut contract,
328                } = {
329                    let file = File::open(contract_path)?;
330                    let reader = BufReader::new(file);
331                    from_reader(reader)?
332                };
333
334                contract.name = contract_name;
335
336                self.add_contract_to_artifact(
337                    &mut artifact,
338                    contract,
339                    chain_id.clone(),
340                    address,
341                    transaction_hash,
342                )?;
343            }
344        }
345
346        Ok(artifact)
347    }
348
349    fn load_artifact<T>(
350        &self,
351        format: Format,
352        origin: impl ToString,
353        source: T,
354        single_loader: impl FnOnce(T) -> serde_json::Result<HardHatExport>,
355        multi_loader: impl FnOnce(T) -> serde_json::Result<HardHatMultiExport>,
356    ) -> Result<Artifact, ArtifactError> {
357        let origin = self.origin.clone().unwrap_or_else(|| origin.to_string());
358
359        let mut artifact = Artifact::with_origin(origin);
360
361        match format {
362            Format::SingleExport => {
363                let loaded = single_loader(source)?;
364                self.fill_artifact(&mut artifact, loaded)?
365            }
366            Format::MultiExport => {
367                let loaded = multi_loader(source)?;
368                self.fill_artifact_multi(&mut artifact, loaded)?
369            }
370        }
371
372        Ok(artifact)
373    }
374
375    fn fill_artifact(
376        &self,
377        artifact: &mut Artifact,
378        export: HardHatExport,
379    ) -> Result<(), ArtifactError> {
380        if self.network_allowed(&export.chain_id, &export.chain_name) {
381            for (name, contract) in export.contracts {
382                let HardHatContract {
383                    address,
384                    transaction_hash,
385                    mut contract,
386                } = contract;
387
388                if !self.contract_allowed(&name) {
389                    continue;
390                }
391
392                contract.name = name;
393
394                self.add_contract_to_artifact(
395                    artifact,
396                    contract,
397                    export.chain_id.clone(),
398                    address,
399                    transaction_hash,
400                )?;
401            }
402        }
403
404        Ok(())
405    }
406
407    fn fill_artifact_multi(
408        &self,
409        artifact: &mut Artifact,
410        export: HardHatMultiExport,
411    ) -> Result<(), ArtifactError> {
412        for (_, export) in export.networks {
413            for (_, export) in export {
414                self.fill_artifact(artifact, export)?;
415            }
416        }
417
418        Ok(())
419    }
420
421    fn add_contract_to_artifact(
422        &self,
423        artifact: &mut Artifact,
424        contract: Contract,
425        chain_id: String,
426        address: Address,
427        transaction_hash: Option<TransactionHash>,
428    ) -> Result<(), ArtifactError> {
429        let contract_guard = artifact.get_mut(&contract.name);
430        let mut contract = if let Some(existing_contract) = contract_guard {
431            if existing_contract.interface != contract.interface {
432                return Err(ArtifactError::AbiMismatch(contract.name));
433            }
434
435            existing_contract
436        } else {
437            // `Drop` of the contract guard can update the underlying contract which can lead
438            // to borrowing issues. To work around those we manually drop the guard here.
439            drop(contract_guard);
440            artifact.insert(contract).inserted_contract
441        };
442
443        let deployment_information = transaction_hash.map(DeploymentInformation::TransactionHash);
444
445        if contract.networks.contains_key(&chain_id) {
446            Err(ArtifactError::DuplicateChain(chain_id))
447        } else {
448            contract.networks_mut().insert(
449                chain_id,
450                Network {
451                    address,
452                    deployment_information,
453                },
454            );
455
456            Ok(())
457        }
458    }
459
460    fn contract_allowed(&self, name: &str) -> bool {
461        !self.contract_explicitly_denied(name)
462            && (self.contracts_allow_list.is_empty() || self.contract_explicitly_allowed(name))
463    }
464
465    fn contract_explicitly_allowed(&self, name: &str) -> bool {
466        self.contracts_allow_list.iter().any(|x| x == name)
467    }
468
469    fn contract_explicitly_denied(&self, name: &str) -> bool {
470        self.contracts_deny_list.iter().any(|x| x == name)
471    }
472
473    fn network_allowed(&self, chain_id: &str, chain_name: &str) -> bool {
474        !self.network_explicitly_denied(chain_id, chain_name)
475            && (self.networks_allow_list.is_empty()
476                || self.network_explicitly_allowed(chain_id, chain_name))
477    }
478
479    fn network_explicitly_allowed(&self, chain_id: &str, chain_name: &str) -> bool {
480        self.networks_allow_list
481            .iter()
482            .any(|x| x.matches(chain_id, chain_name))
483    }
484
485    fn network_explicitly_denied(&self, chain_id: &str, chain_name: &str) -> bool {
486        self.networks_deny_list
487            .iter()
488            .any(|x| x.matches(chain_id, chain_name))
489    }
490}
491
492impl Default for HardHatLoader {
493    fn default() -> Self {
494        HardHatLoader::new()
495    }
496}
497
498/// Artifact format.
499#[derive(Copy, Clone, Debug, Eq, PartialEq)]
500pub enum Format {
501    /// Contracts for a single network. Generated with `hardhat export`.
502    SingleExport,
503
504    /// Contracts for all networks. Generated with `hardhat export --export-all`.
505    MultiExport,
506}
507
508/// Network allow-deny entry.
509#[derive(Clone, Debug)]
510pub enum NetworkEntry {
511    /// Network identified by chain ID.
512    ByChainId(String),
513
514    /// Network identified by its name specified in `hardhat.config.js`.
515    ByName(String),
516}
517
518impl NetworkEntry {
519    fn matches(&self, chain_id: &str, chain_name: &str) -> bool {
520        match self {
521            NetworkEntry::ByChainId(id) => chain_id == id,
522            NetworkEntry::ByName(name) => chain_name == name,
523        }
524    }
525}
526
527#[derive(Deserialize)]
528struct HardHatMultiExport {
529    #[serde(flatten)]
530    networks: HashMap<String, HashMap<String, HardHatExport>>,
531}
532
533#[derive(Deserialize)]
534struct HardHatExport {
535    #[serde(rename = "name")]
536    chain_name: String,
537    #[serde(rename = "chainId")]
538    chain_id: String,
539    contracts: HashMap<String, HardHatContract>,
540}
541
542#[derive(Deserialize)]
543struct HardHatContract {
544    address: Address,
545    #[serde(rename = "transactionHash")]
546    transaction_hash: Option<TransactionHash>,
547    #[serde(flatten)]
548    contract: Contract,
549}
550
551#[cfg(test)]
552mod test {
553    use super::*;
554    use std::path::PathBuf;
555    use web3::ethabi::ethereum_types::BigEndianHash;
556    use web3::types::{H256, U256};
557
558    fn address(address: u8) -> Address {
559        Address::from(H256::from_uint(&U256::from(address)))
560    }
561
562    #[test]
563    fn load_single() {
564        let json = r#"
565          {
566            "name": "mainnet",
567            "chainId": "1",
568            "contracts": {
569              "A": {
570                "address": "0x000000000000000000000000000000000000000A"
571              },
572              "B": {
573                "address": "0x000000000000000000000000000000000000000B"
574              }
575            }
576          }
577        "#;
578
579        let artifact = HardHatLoader::new()
580            .load_from_str(Format::SingleExport, json)
581            .unwrap();
582
583        assert_eq!(artifact.len(), 2);
584
585        let a = artifact.get("A").unwrap();
586        assert_eq!(a.name, "A");
587        assert_eq!(a.networks.len(), 1);
588        assert_eq!(a.networks["1"].address, address(0xA));
589
590        let b = artifact.get("B").unwrap();
591        assert_eq!(b.name, "B");
592        assert_eq!(b.networks.len(), 1);
593        assert_eq!(b.networks["1"].address, address(0xB));
594    }
595
596    static MULTI_EXPORT: &str = r#"
597      {
598        "1": {
599          "mainnet": {
600            "name": "mainnet",
601            "chainId": "1",
602            "contracts": {
603              "A": {
604                "address": "0x000000000000000000000000000000000000000A"
605              },
606              "B": {
607                "address": "0x000000000000000000000000000000000000000B"
608              }
609            }
610          }
611        },
612        "4": {
613          "rinkeby": {
614            "name": "rinkeby",
615            "chainId": "4",
616            "contracts": {
617              "A": {
618                "address": "0x00000000000000000000000000000000000000AA"
619              }
620            }
621          }
622        }
623      }
624    "#;
625
626    #[test]
627    fn load_multi() {
628        let artifact = HardHatLoader::new()
629            .load_from_str(Format::MultiExport, MULTI_EXPORT)
630            .unwrap();
631
632        assert_eq!(artifact.len(), 2);
633
634        let a = artifact.get("A").unwrap();
635        assert_eq!(a.name, "A");
636        assert_eq!(a.networks.len(), 2);
637        assert_eq!(a.networks["1"].address, address(0xA));
638        assert_eq!(a.networks["4"].address, address(0xAA));
639
640        let b = artifact.get("B").unwrap();
641        assert_eq!(b.name, "B");
642        assert_eq!(b.networks.len(), 1);
643        assert_eq!(b.networks["1"].address, address(0xB));
644    }
645
646    #[test]
647    fn load_multi_duplicate_networks_ok() {
648        let json = r#"
649          {
650            "1": {
651              "mainnet": {
652                "name": "mainnet",
653                "chainId": "1",
654                "contracts": {
655                  "A": {
656                    "address": "0x000000000000000000000000000000000000000A"
657                  }
658                }
659              },
660              "mainnet_beta": {
661                "name": "mainnet_beta",
662                "chainId": "1",
663                "contracts": {
664                  "B": {
665                    "address": "0x000000000000000000000000000000000000000B"
666                  }
667                }
668              }
669            }
670          }
671        "#;
672
673        let artifact = HardHatLoader::new()
674            .load_from_str(Format::MultiExport, json)
675            .unwrap();
676
677        assert_eq!(artifact.len(), 2);
678
679        let a = artifact.get("A").unwrap();
680        assert_eq!(a.name, "A");
681        assert_eq!(a.networks.len(), 1);
682        assert_eq!(a.networks["1"].address, address(0xA));
683
684        let b = artifact.get("B").unwrap();
685        assert_eq!(b.name, "B");
686        assert_eq!(b.networks.len(), 1);
687        assert_eq!(b.networks["1"].address, address(0xB));
688    }
689
690    #[test]
691    fn load_multi_duplicate_networks_err() {
692        let json = r#"
693          {
694            "1": {
695              "mainnet": {
696                "name": "mainnet",
697                "chainId": "1",
698                "contracts": {
699                  "A": {
700                    "address": "0x000000000000000000000000000000000000000A"
701                  }
702                }
703              },
704              "mainnet_beta": {
705                "name": "mainnet_beta",
706                "chainId": "1",
707                "contracts": {
708                  "A": {
709                    "address": "0x00000000000000000000000000000000000000AA"
710                  }
711                }
712              }
713            }
714          }
715        "#;
716
717        let err = HardHatLoader::new().load_from_str(Format::MultiExport, json);
718
719        match err {
720            Err(ArtifactError::DuplicateChain(chain_id)) => assert_eq!(chain_id, "1"),
721            Err(unexpected_err) => panic!("unexpected error {:?}", unexpected_err),
722            _ => panic!("didn't throw an error"),
723        }
724    }
725
726    #[test]
727    fn load_multi_mismatching_abi() {
728        let json = r#"
729          {
730            "1": {
731              "mainnet": {
732                "name": "mainnet",
733                "chainId": "1",
734                "contracts": {
735                  "A": {
736                    "address": "0x000000000000000000000000000000000000000A",
737                    "abi": [
738                      {
739                        "constant": false,
740                        "inputs": [],
741                        "name": "foo",
742                        "outputs": [],
743                        "payable": false,
744                        "stateMutability": "nonpayable",
745                        "type": "function"
746                      }
747                    ]
748                  }
749                }
750              }
751            },
752            "4": {
753              "rinkeby": {
754                "name": "rinkeby",
755                "chainId": "4",
756                "contracts": {
757                  "A": {
758                    "address": "0x00000000000000000000000000000000000000AA",
759                    "abi": [
760                      {
761                        "constant": false,
762                        "inputs": [],
763                        "name": "bar",
764                        "outputs": [],
765                        "payable": false,
766                        "stateMutability": "nonpayable",
767                        "type": "function"
768                      }
769                    ]
770                  }
771                }
772              }
773            }
774          }
775        "#;
776
777        let err = HardHatLoader::new().load_from_str(Format::MultiExport, json);
778
779        match err {
780            Err(ArtifactError::AbiMismatch(name)) => assert_eq!(name, "A"),
781            Err(unexpected_err) => panic!("unexpected error {:?}", unexpected_err),
782            _ => panic!("didn't throw an error"),
783        }
784    }
785
786    static NETWORK_CONFLICTS: &str = r#"
787      {
788        "1": {
789          "mainnet": {
790            "name": "mainnet",
791            "chainId": "1",
792            "contracts": {
793              "A": {
794                "address": "0x000000000000000000000000000000000000000A"
795              }
796            }
797          },
798          "mainnet_beta": {
799            "name": "mainnet_beta",
800            "chainId": "1",
801            "contracts": {
802              "A": {
803                "address": "0x00000000000000000000000000000000000000AA",
804                "abi": [
805                  {
806                    "constant": false,
807                    "inputs": [],
808                    "name": "test_method",
809                    "outputs": [],
810                    "payable": false,
811                    "stateMutability": "nonpayable",
812                    "type": "function"
813                  }
814                ]
815              }
816            }
817          }
818        },
819        "4": {
820          "rinkeby": {
821            "name": "rinkeby",
822            "chainId": "4",
823            "contracts": {
824              "A": {
825                "address": "0x00000000000000000000000000000000000000BA"
826              }
827            }
828          }
829        }
830      }
831    "#;
832
833    #[test]
834    fn load_multi_allow_by_name() {
835        let artifact = HardHatLoader::new()
836            .allow_network_by_name("mainnet")
837            .allow_network_by_name("rinkeby")
838            .load_from_str(Format::MultiExport, NETWORK_CONFLICTS)
839            .unwrap();
840
841        assert_eq!(artifact.len(), 1);
842
843        let a = artifact.get("A").unwrap();
844        assert_eq!(a.name, "A");
845        assert_eq!(a.networks.len(), 2);
846        assert_eq!(a.networks["1"].address, address(0xA));
847        assert_eq!(a.networks["4"].address, address(0xBA));
848    }
849
850    #[test]
851    fn load_multi_allow_by_chain_id() {
852        let artifact = HardHatLoader::new()
853            .allow_network_by_chain_id("4")
854            .load_from_str(Format::MultiExport, NETWORK_CONFLICTS)
855            .unwrap();
856
857        assert_eq!(artifact.len(), 1);
858
859        let a = artifact.get("A").unwrap();
860        assert_eq!(a.name, "A");
861        assert_eq!(a.networks.len(), 1);
862        assert_eq!(a.networks["4"].address, address(0xBA));
863    }
864
865    #[test]
866    fn load_multi_deny_by_name() {
867        let artifact = HardHatLoader::new()
868            .deny_network_by_name("mainnet_beta")
869            .load_from_str(Format::MultiExport, NETWORK_CONFLICTS)
870            .unwrap();
871
872        assert_eq!(artifact.len(), 1);
873
874        let a = artifact.get("A").unwrap();
875        assert_eq!(a.name, "A");
876        assert_eq!(a.networks.len(), 2);
877        assert_eq!(a.networks["1"].address, address(0xA));
878        assert_eq!(a.networks["4"].address, address(0xBA));
879    }
880
881    #[test]
882    fn load_multi_deny_by_chain_id() {
883        let artifact = HardHatLoader::new()
884            .deny_network_by_chain_id("1")
885            .load_from_str(Format::MultiExport, NETWORK_CONFLICTS)
886            .unwrap();
887
888        assert_eq!(artifact.len(), 1);
889
890        let a = artifact.get("A").unwrap();
891        assert_eq!(a.name, "A");
892        assert_eq!(a.networks.len(), 1);
893        assert_eq!(a.networks["4"].address, address(0xBA));
894    }
895
896    #[test]
897    fn load_multi_allow_contract_name() {
898        let artifact = HardHatLoader::new()
899            .allow_contract("A")
900            .load_from_str(Format::MultiExport, MULTI_EXPORT)
901            .unwrap();
902
903        assert_eq!(artifact.len(), 1);
904
905        let a = artifact.get("A").unwrap();
906        assert_eq!(a.name, "A");
907        assert_eq!(a.networks.len(), 2);
908        assert_eq!(a.networks["1"].address, address(0xA));
909        assert_eq!(a.networks["4"].address, address(0xAA));
910
911        let artifact = HardHatLoader::new()
912            .allow_contract("X")
913            .load_from_str(Format::MultiExport, MULTI_EXPORT)
914            .unwrap();
915
916        assert_eq!(artifact.len(), 0);
917    }
918
919    #[test]
920    fn load_multi_deny_contract_name() {
921        let artifact = HardHatLoader::new()
922            .deny_contract("A")
923            .load_from_str(Format::MultiExport, MULTI_EXPORT)
924            .unwrap();
925
926        assert_eq!(artifact.len(), 1);
927
928        let a = artifact.get("B").unwrap();
929        assert_eq!(a.name, "B");
930        assert_eq!(a.networks.len(), 1);
931        assert_eq!(a.networks["1"].address, address(0xB));
932
933        let artifact = HardHatLoader::new()
934            .deny_contract("X")
935            .load_from_str(Format::MultiExport, MULTI_EXPORT)
936            .unwrap();
937
938        assert_eq!(artifact.len(), 2);
939
940        let a = artifact.get("A").unwrap();
941        assert_eq!(a.name, "A");
942        assert_eq!(a.networks.len(), 2);
943        assert_eq!(a.networks["1"].address, address(0xA));
944        assert_eq!(a.networks["4"].address, address(0xAA));
945
946        let b = artifact.get("B").unwrap();
947        assert_eq!(b.name, "B");
948        assert_eq!(b.networks.len(), 1);
949        assert_eq!(b.networks["1"].address, address(0xB));
950    }
951
952    fn hardhat_dir() -> PathBuf {
953        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
954        path.push("../examples/hardhat/deployments");
955        path
956    }
957
958    #[test]
959    fn load_from_directory() {
960        let artifact = HardHatLoader::new()
961            .load_from_directory(hardhat_dir())
962            .unwrap();
963
964        assert_eq!(artifact.len(), 1);
965
966        let a = artifact.get("DeployedContract").unwrap();
967        assert_eq!(a.name, "DeployedContract");
968        assert_eq!(a.networks.len(), 2);
969        assert_eq!(
970            a.networks["4"].address,
971            "0x4E29B76eC7d20c58A6B156CB464594a4ae39FdEd"
972                .parse()
973                .unwrap()
974        );
975        assert_eq!(
976            a.networks["4"].deployment_information,
977            Some(DeploymentInformation::TransactionHash(
978                "0x0122d15a8d394b8f9e45c15b7d3e5365bbf7122a15952246676e2fe7eb858f35"
979                    .parse()
980                    .unwrap()
981            ))
982        );
983        assert_eq!(
984            a.networks["1337"].address,
985            "0x29BE0588389993e7064C21f00761303eb51373F5"
986                .parse()
987                .unwrap()
988        );
989        assert_eq!(
990            a.networks["1337"].deployment_information,
991            Some(DeploymentInformation::TransactionHash(
992                "0xe0631d7f749fe73f94e59f6e25ff9b925980e8e29ed67b8f862ec76a783ea06e"
993                    .parse()
994                    .unwrap()
995            ))
996        );
997    }
998}