Skip to content

Commit

Permalink
Add Artifact struct (#544)
Browse files Browse the repository at this point in the history
Part of #512.

We're adding the Artifact trait that will provide a uniform interface for working with different artifact sources.
  • Loading branch information
Tamika Nomara authored Jun 24, 2021
1 parent 540a9c2 commit bfdff6f
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 5 deletions.
101 changes: 101 additions & 0 deletions ethcontract-common/src/artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Tools for loading artifacts that contain compiled contracts.
//!
//! Artifacts come in various shapes and sizes, but usually they
//! are JSON files containing one or multiple compiled contracts
//! as well as their deployment information.
//!
//! This module provides trait [`Artifact`] that encapsulates different
//! artifact models. It also provides tools to load artifacts from different
//! sources, and parse them using different formats.
use crate::Contract;
use std::collections::HashMap;

pub mod truffle;

/// An entity that contains compiled contracts.
pub struct Artifact {
origin: String,
contracts: HashMap<String, Contract>,
}

impl Artifact {
/// Create a new empty artifact.
pub fn new() -> Self {
Artifact {
origin: "<unknown>".to_string(),
contracts: HashMap::new(),
}
}

/// Create a new artifact with an origin information.
pub fn with_origin(origin: String) -> Self {
Artifact {
origin,
contracts: HashMap::new(),
}
}

/// Describe where this artifact comes from.
///
/// This function is used when a human-readable reference to the artifact
/// is required. It could be anything: path to a json file, url, etc.
pub fn origin(&self) -> &str {
&self.origin
}

/// Set new origin for the artifact.
///
/// Artifact loaders will set origin to something meaningful in most cases,
/// so this function should not be used often. There are cases when
/// it is required, though.
pub fn set_origin(&mut self, origin: impl Into<String>) {
self.origin = origin.into();
}

/// Check whether this artifact has a contract with the given name.
pub fn contains(&self, name: &str) -> bool {
self.contracts.contains_key(name)
}

/// Get contract by name.
///
/// Some artifact formats allow exporting a single unnamed contract.
/// In this case, the contract will have an empty string as its name.
pub fn get(&self, name: &str) -> Option<&Contract> {
self.contracts.get(name)
}

/// 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)
}

/// Remove contract from the artifact.
///
/// Returns removed contract or [`None`] if contract with the given name
/// wasn't found.
pub fn remove(&mut self, name: &str) -> Option<Contract> {
self.contracts.remove(name)
}

/// Create an iterator that yields the artifact's contracts.
pub fn iter(&self) -> impl Iterator<Item = &Contract> + '_ {
self.contracts.values()
}

/// Take all contracts from the artifact, leaving it empty,
/// and iterate over them.
pub fn drain(&mut self) -> impl Iterator<Item = Contract> + '_ {
self.contracts.drain().map(|(_, contract)| contract)
}
}

impl Default for Artifact {
fn default() -> Self {
Artifact::new()
}
}
133 changes: 133 additions & 0 deletions ethcontract-common/src/artifact/truffle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! Implements the most common artifact format used in Truffle, Waffle
//! and some other libraries.
//!
//! This artifact is represented as a JSON file containing information about
//! a single contract. We parse the following fields:
//!
//! - `contractName`: name of the contract (optional);
//! - `abi`: information about contract's interface;
//! - `bytecode`: contract's compiled bytecode (optional);
//! - `networks`: info about known contract deployments (optional);
//! - `devdoc`, `userdoc`: additional documentation for contract's methods.
use crate::artifact::Artifact;
use crate::errors::ArtifactError;
use crate::Contract;
use serde_json::Value;
use std::fs::File;
use std::path::Path;

/// Loads truffle artifacts.
pub struct TruffleLoader {
/// Override for artifact's origin.
///
/// If empty, origin will be derived automatically.
pub origin: Option<String>,

/// Override for contract's name.
///
/// Truffle artifacts contain a single contract which may
pub name: Option<String>,
}

impl TruffleLoader {
/// Create a new truffle loader.
pub fn new() -> Self {
TruffleLoader {
origin: None,
name: None,
}
}

/// Create a new truffle loader and set an override for artifact's origins.
pub fn with_origin(origin: String) -> Self {
TruffleLoader {
origin: Some(origin),
name: None,
}
}

/// Set new override for artifact's origin. See [`origin`] for more info.
///
/// [`origin`]: #structfield.origin
#[inline]
pub fn origin(mut self, origin: String) -> Self {
self.origin = Some(origin);
self
}

/// Set new override for artifact's name. See [`name`] for more info.
///
/// [`name`]: #structfield.name
#[inline]
pub fn name(mut self, name: String) -> Self {
self.name = Some(name);
self
}

/// Parse a truffle artifact from JSON string.
pub fn load_from_string(&self, json: &str) -> Result<Artifact, ArtifactError> {
let origin = self
.origin
.clone()
.unwrap_or_else(|| "<memory>".to_string());
let mut artifact = Artifact::with_origin(origin);
artifact.insert(self.load_contract_from_string(json)?);
Ok(artifact)
}

/// Parse a contract from JSON string.
pub fn load_contract_from_string(&self, json: &str) -> Result<Contract, ArtifactError> {
let mut contract: Contract = serde_json::from_str(json)?;
if let Some(name) = &self.name {
contract.name = name.clone();
}
Ok(contract)
}

/// Loads a truffle artifact from JSON value.
pub fn load_from_json(&self, value: Value) -> Result<Artifact, ArtifactError> {
let origin = self
.origin
.clone()
.unwrap_or_else(|| "<memory>".to_string());
let mut artifact = Artifact::with_origin(origin);
artifact.insert(self.load_contract_from_json(value)?);
Ok(artifact)
}

/// Loads a contract from JSON value.
pub fn load_contract_from_json(&self, value: Value) -> Result<Contract, ArtifactError> {
let mut contract: Contract = serde_json::from_value(value)?;
if let Some(name) = &self.name {
contract.name = name.clone();
}
Ok(contract)
}

/// Loads a truffle artifact from disk.
pub fn load_from_file(&self, path: &Path) -> Result<Artifact, ArtifactError> {
let origin = self
.origin
.clone()
.unwrap_or_else(|| path.display().to_string());
let mut artifact = Artifact::with_origin(origin);
artifact.insert(self.load_contract_from_file(path)?);
Ok(artifact)
}

/// Loads a contract from disk.
pub fn load_contract_from_file(&self, path: &Path) -> Result<Contract, ArtifactError> {
let mut contract: Contract = serde_json::from_reader(File::open(path)?)?;
if let Some(name) = &self.name {
contract.name = name.clone();
}
Ok(contract)
}
}

impl Default for TruffleLoader {
fn default() -> Self {
TruffleLoader::new()
}
}
6 changes: 3 additions & 3 deletions ethcontract-common/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use web3::types::Address;
#[derive(Clone, Debug, Deserialize)]
#[serde(default = "Contract::empty")]
pub struct Contract {
/// The contract name
/// The contract name. Unnamed contracts have an empty string as their name.
#[serde(rename = "contractName")]
pub contract_name: String,
pub name: String,
/// The contract ABI
pub abi: Abi,
/// The contract deployment bytecode.
Expand All @@ -32,7 +32,7 @@ impl Contract {
/// Creates an empty contract instance.
pub fn empty() -> Self {
Contract {
contract_name: String::new(),
name: String::new(),
abi: Abi {
constructor: None,
functions: HashMap::new(),
Expand Down
1 change: 1 addition & 0 deletions ethcontract-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! the `ethcontract-derive` crate.
pub mod abiext;
pub mod artifact;
pub mod bytecode;
pub mod contract;
pub mod errors;
Expand Down
4 changes: 2 additions & 2 deletions ethcontract-generate/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ impl Context {

let raw_contract_name = if let Some(name) = args.contract_name_override.as_ref() {
name
} else if !contract.contract_name.is_empty() {
&contract.contract_name
} else if !contract.name.is_empty() {
&contract.name
} else {
return Err(anyhow!(
"contract artifact is missing a name, this can happen when \
Expand Down

0 comments on commit bfdff6f

Please sign in to comment.