#![deny(missing_docs)]
mod common;
mod deployment;
mod events;
mod methods;
mod types;
use crate::util;
use crate::Args;
use anyhow::{anyhow, Context as _, Result};
use ethcontract_common::{Address, Artifact};
use inflector::Inflector;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use std::collections::HashMap;
use syn::{Path, Visibility};
pub(crate) struct Context {
artifact_json: Literal,
artifact: Artifact,
runtime_crate: Ident,
visibility: Visibility,
contract_mod: Ident,
contract_name: Ident,
deployments: HashMap<u32, Address>,
method_aliases: HashMap<String, Ident>,
event_derives: Vec<Path>,
}
impl Context {
fn from_args(args: Args) -> Result<Self> {
let (artifact_json, artifact) = {
let artifact_json = args
.artifact_source
.artifact_json()
.context("failed to get artifact JSON")?;
let artifact = Artifact::from_json(&artifact_json)
.with_context(|| format!("invalid artifact JSON '{}'", artifact_json))
.with_context(|| {
format!(
"failed to parse artifact from source {:?}",
args.artifact_source,
)
})?;
(Literal::string(&artifact_json), artifact)
};
let raw_contract_name = if let Some(name) = args.contract_name_override.as_ref() {
name
} else if !artifact.contract_name.is_empty() {
&artifact.contract_name
} else {
return Err(anyhow!(
"contract artifact is missing a name, this can happen when \
using a source that does not provide a contract name such as \
Etherscan; in this case the contract must be manually \
specified"
));
};
let runtime_crate = util::ident(&args.runtime_crate_name);
let visibility = match args.visibility_modifier.as_ref() {
Some(vis) => syn::parse_str(vis)?,
None => Visibility::Inherited,
};
let contract_mod = if let Some(name) = args.contract_mod_override.as_ref() {
util::ident(name)
} else {
util::ident(&raw_contract_name.to_snake_case())
};
let contract_name = util::ident(raw_contract_name);
let mut method_aliases = HashMap::new();
for (signature, alias) in args.method_aliases.into_iter() {
let alias = syn::parse_str(&alias)?;
if method_aliases.insert(signature.clone(), alias).is_some() {
return Err(anyhow!(
"duplicate method signature '{}' in method aliases",
signature,
));
}
}
let event_derives = args
.event_derives
.iter()
.map(|derive| syn::parse_str::<Path>(derive))
.collect::<Result<Vec<_>, _>>()
.context("failed to parse event derives")?;
Ok(Context {
artifact_json,
artifact,
runtime_crate,
visibility,
contract_mod,
contract_name,
deployments: args.deployments,
method_aliases,
event_derives,
})
}
}
#[cfg(test)]
impl Default for Context {
fn default() -> Self {
Context {
artifact_json: Literal::string("{}"),
artifact: Artifact::empty(),
runtime_crate: util::ident("ethcontract"),
visibility: Visibility::Inherited,
contract_mod: util::ident("contract"),
contract_name: util::ident("Contract"),
deployments: HashMap::new(),
method_aliases: HashMap::new(),
event_derives: Vec::new(),
}
}
}
pub(crate) fn expand(args: Args) -> Result<TokenStream> {
let cx = Context::from_args(args)?;
let contract = expand_contract(&cx).context("error expanding contract from its ABI")?;
Ok(contract)
}
fn expand_contract(cx: &Context) -> Result<TokenStream> {
let runtime_crate = &cx.runtime_crate;
let vis = &cx.visibility;
let contract_mod = &cx.contract_mod;
let contract_name = &cx.contract_name;
let common = common::expand(cx);
let deployment = deployment::expand(cx)?;
let methods = methods::expand(cx)?;
let events = events::expand(cx)?;
Ok(quote! {
#[allow(dead_code)]
#vis mod #contract_mod {
#[rustfmt::skip]
use #runtime_crate as ethcontract;
#common
#deployment
#methods
#events
}
#vis use self::#contract_mod::Contract as #contract_name;
})
}