Skip to content

Commit

Permalink
Implement artifact loader for hardhat export (#548)
Browse files Browse the repository at this point in the history
Part of #512.

Implements artifact loader for hardhat export (hardhat export command).
  • Loading branch information
Tamika Nomara authored Jun 25, 2021
1 parent bfdff6f commit eada4dd
Show file tree
Hide file tree
Showing 4 changed files with 908 additions and 59 deletions.
164 changes: 158 additions & 6 deletions ethcontract-common/src/artifact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
//! artifact models. It also provides tools to load artifacts from different
//! sources, and parse them using different formats.
use crate::Contract;
use crate::contract::{Documentation, Network};
use crate::{Abi, Bytecode, Contract};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::ops::Deref;

pub mod hardhat;
pub mod truffle;

/// An entity that contains compiled contracts.
Expand All @@ -29,9 +33,9 @@ impl Artifact {
}

/// Create a new artifact with an origin information.
pub fn with_origin(origin: String) -> Self {
pub fn with_origin(origin: impl Into<String>) -> Self {
Artifact {
origin,
origin: origin.into(),
contracts: HashMap::new(),
}
}
Expand All @@ -53,6 +57,16 @@ impl Artifact {
self.origin = origin.into();
}

/// Get number of contracts contained in this artifact.
pub fn len(&self) -> usize {
self.contracts.len()
}

/// Check if this artifact contains no contracts.
pub fn is_empty(&self) -> bool {
self.contracts.is_empty()
}

/// Check whether this artifact has a contract with the given name.
pub fn contains(&self, name: &str) -> bool {
self.contracts.contains_key(name)
Expand All @@ -66,12 +80,33 @@ impl Artifact {
self.contracts.get(name)
}

/// Get contract by name.
///
/// Returns a handle that allows mutating the contract. It does not allow
/// renaming contract though. For that, you'll need to remove
/// it and add again.
pub fn get_mut(&mut self, name: &str) -> Option<ContractMut> {
self.contracts.get_mut(name).map(ContractMut)
}

/// Insert a new contract to the artifact.
///
/// If contract with this name already exists, replace it
/// and return an old contract.
pub fn insert(&mut self, contract: Contract) -> Option<Contract> {
self.contracts.insert(contract.name.clone(), contract)
/// and return the old contract.
pub fn insert(&mut self, contract: Contract) -> InsertResult {
match self.contracts.entry(contract.name.clone()) {
Entry::Occupied(mut o) => {
let old_contract = o.insert(contract);
InsertResult {
inserted_contract: ContractMut(o.into_mut()),
old_contract: Some(old_contract),
}
}
Entry::Vacant(v) => InsertResult {
inserted_contract: ContractMut(v.insert(contract)),
old_contract: None,
},
}
}

/// Remove contract from the artifact.
Expand Down Expand Up @@ -99,3 +134,120 @@ impl Default for Artifact {
Artifact::new()
}
}

/// Result of inserting a nre contract into an artifact.
pub struct InsertResult<'a> {
/// Reference to the newly inserted contract.
pub inserted_contract: ContractMut<'a>,

/// If insert operation replaced an old contract, it will appear here.
pub old_contract: Option<Contract>,
}

/// A wrapper that allows mutating contract
/// but doesn't allow changing its name.
pub struct ContractMut<'a>(&'a mut Contract);

impl<'a> ContractMut<'a> {
/// Get mutable access to abi.
pub fn abi_mut(&mut self) -> &mut Abi {
&mut self.0.abi
}

/// Get mutable access to bytecode.
pub fn bytecode_mut(&mut self) -> &mut Bytecode {
&mut self.0.bytecode
}

/// Get mutable access to networks.
pub fn networks_mut(&mut self) -> &mut HashMap<String, Network> {
&mut self.0.networks
}

/// Get mutable access to devdoc.
pub fn devdoc_mut(&mut self) -> &mut Documentation {
&mut self.0.devdoc
}

/// Get mutable access to userdoc.
pub fn userdoc_mut(&mut self) -> &mut Documentation {
&mut self.0.userdoc
}
}

impl Deref for ContractMut<'_> {
type Target = Contract;

fn deref(&self) -> &Self::Target {
self.0
}
}

#[cfg(test)]
mod test {
use super::*;

fn make_contract(name: &str) -> Contract {
let mut contract = Contract::empty();
contract.name = name.to_string();
contract
}

#[test]
fn insert() {
let mut artifact = Artifact::new();

assert_eq!(artifact.len(), 0);

let insert_res = artifact.insert(make_contract("C1"));

assert_eq!(insert_res.inserted_contract.name, "C1");
assert!(insert_res.old_contract.is_none());

assert_eq!(artifact.len(), 1);
assert!(artifact.contains("C1"));

let insert_res = artifact.insert(make_contract("C2"));

assert_eq!(insert_res.inserted_contract.name, "C2");
assert!(insert_res.old_contract.is_none());

assert_eq!(artifact.len(), 2);
assert!(artifact.contains("C2"));

let insert_res = artifact.insert(make_contract("C1"));

assert_eq!(insert_res.inserted_contract.name, "C1");
assert!(insert_res.old_contract.is_some());

assert_eq!(artifact.len(), 2);
}

#[test]
fn remove() {
let mut artifact = Artifact::new();

artifact.insert(make_contract("C1"));
artifact.insert(make_contract("C2"));

assert_eq!(artifact.len(), 2);
assert!(artifact.contains("C1"));
assert!(artifact.contains("C2"));

let c0 = artifact.remove("C0");
assert!(c0.is_none());

assert_eq!(artifact.len(), 2);
assert!(artifact.contains("C1"));
assert!(artifact.contains("C2"));

let c1 = artifact.remove("C1");

assert!(c1.is_some());
assert_eq!(c1.unwrap().name, "C1");

assert_eq!(artifact.len(), 1);
assert!(!artifact.contains("C1"));
assert!(artifact.contains("C2"));
}
}
Loading

0 comments on commit eada4dd

Please sign in to comment.