ethers-rs/ethers-contract/ethers-contract-abigen/src/contract.rs

153 lines
4.7 KiB
Rust
Raw Normal View History

2020-05-26 18:57:59 +00:00
#![deny(missing_docs)]
mod common;
mod events;
mod methods;
mod types;
use super::util;
use super::Abigen;
2020-05-26 18:57:59 +00:00
use anyhow::{anyhow, Context as _, Result};
use ethers_core::{
abi::{parse_abi, Abi},
types::Address,
};
2020-05-26 18:57:59 +00:00
use inflector::Inflector;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
use std::collections::BTreeMap;
2020-05-26 18:57:59 +00:00
use syn::{Path, Visibility};
/// Internal shared context for generating smart contract bindings.
pub(crate) struct Context {
/// The ABI string pre-parsing.
abi_str: Literal,
/// The parsed ABI.
abi: Abi,
/// Was the ABI in human readable format?
human_readable: bool,
2020-05-26 18:57:59 +00:00
/// The contract name as an identifier.
contract_name: Ident,
/// Manually specified method aliases.
method_aliases: BTreeMap<String, Ident>,
2020-05-26 18:57:59 +00:00
/// Derives added to event structs and enums.
event_derives: Vec<Path>,
}
impl Context {
pub(crate) fn expand(args: Abigen) -> Result<TokenStream> {
let cx = Self::from_abigen(args)?;
2020-05-26 18:57:59 +00:00
let name = &cx.contract_name;
let name_mod = util::ident(&format!(
"{}_mod",
cx.contract_name.to_string().to_lowercase()
));
2020-06-02 21:10:46 +00:00
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
2020-05-26 18:57:59 +00:00
// 0. Imports
let imports = common::imports(&name.to_string());
2020-05-26 18:57:59 +00:00
// 1. Declare Contract struct
2020-06-02 21:10:46 +00:00
let struct_decl = common::struct_declaration(&cx, &abi_name);
2020-05-26 18:57:59 +00:00
// 2. Declare events structs & impl FromTokens for each event
let events_decl = cx.events_declaration()?;
// 3. impl block for the event functions
let contract_events = cx.events()?;
// 4. impl block for the contract methods
let contract_methods = cx.methods()?;
Ok(quote! {
// export all the created data types
pub use #name_mod::*;
#[allow(clippy::too_many_arguments)]
2020-05-26 18:57:59 +00:00
mod #name_mod {
#imports
#struct_decl
impl<'a, M: Middleware> #name<M> {
2020-05-26 18:57:59 +00:00
/// Creates a new contract instance with the specified `ethers`
/// client at the given `Address`. The contract derefs to a `ethers::Contract`
/// object
pub fn new<T: Into<Address>>(address: T, client: Arc<M>) -> Self {
let contract = Contract::new(address.into(), #abi_name.clone(), client);
2020-05-26 18:57:59 +00:00
Self(contract)
}
// TODO: Implement deployment.
#contract_methods
#contract_events
}
#events_decl
}
})
}
/// Create a context from the code generation arguments.
fn from_abigen(args: Abigen) -> Result<Self> {
2020-05-26 18:57:59 +00:00
// get the actual ABI string
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
// parse it
let (abi, human_readable): (Abi, _) = if let Ok(abi) = serde_json::from_str(&abi_str) {
// normal abi format
(abi, false)
} else {
// heuristic for parsing the human readable format
// replace bad chars
let abi_str = abi_str.replace('[', "").replace(']', "");
// split lines and get only the non-empty things
let split: Vec<&str> = abi_str
.split('\n')
.map(|x| x.trim())
.filter(|x| !x.is_empty())
.collect();
(parse_abi(&split)?, true)
};
2020-05-26 18:57:59 +00:00
let contract_name = util::ident(&args.contract_name);
2020-05-26 18:57:59 +00:00
// NOTE: We only check for duplicate signatures here, since if there are
// duplicate aliases, the compiler will produce a warning because a
// method will be re-defined.
let mut method_aliases = BTreeMap::new();
2020-05-26 18:57:59 +00:00
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 {
abi,
human_readable,
2020-05-26 18:57:59 +00:00
abi_str: Literal::string(&abi_str),
contract_name,
method_aliases,
event_derives,
})
}
}