#![deny(rustdoc::broken_intra_doc_links, missing_docs, unsafe_code)]
#![warn(unreachable_pub)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#[cfg(test)]
#[allow(missing_docs)]
#[macro_use]
#[path = "test/macros.rs"]
mod test_macros;
pub mod contract;
pub use contract::structs::InternalStructs;
pub mod filter;
pub use filter::{ContractFilter, ExcludeContracts, SelectContracts};
pub mod multi;
pub use multi::MultiAbigen;
mod source;
#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
pub use source::Explorer;
pub use source::Source;
mod util;
mod verbatim;
pub use ethers_core::types::Address;
use contract::{Context, ExpandedContract};
use eyre::{Result, WrapErr};
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use std::{collections::HashMap, fmt, fs, io, path::Path};
#[derive(Clone, Debug)]
#[must_use = "Abigen does nothing unless you generate or expand it."]
pub struct Abigen {
abi_source: Source,
contract_name: Ident,
format: bool,
emit_cargo_directives: bool,
method_aliases: HashMap<String, String>,
event_aliases: HashMap<String, String>,
error_aliases: HashMap<String, String>,
derives: Vec<syn::Path>,
}
impl Default for Abigen {
fn default() -> Self {
Self {
abi_source: Source::default(),
contract_name: Ident::new("DefaultContract", proc_macro2::Span::call_site()),
format: true,
emit_cargo_directives: false,
method_aliases: HashMap::new(),
derives: Vec::new(),
event_aliases: HashMap::new(),
error_aliases: HashMap::new(),
}
}
}
impl Abigen {
pub fn new<T: AsRef<str>, S: AsRef<str>>(contract_name: T, abi_source: S) -> Result<Self> {
let abi_source: Source = abi_source.as_ref().parse()?;
Ok(Self {
emit_cargo_directives: abi_source.is_local() && in_build_script(),
abi_source,
contract_name: syn::parse_str(contract_name.as_ref())?,
..Default::default()
})
}
pub fn new_raw(contract_name: Ident, abi_source: Source) -> Self {
Self {
emit_cargo_directives: abi_source.is_local() && in_build_script(),
abi_source,
contract_name,
..Default::default()
}
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let path = path
.to_str()
.ok_or_else(|| eyre::eyre!("path is not valid UTF-8: {}", path.display()))?;
let source = Source::local(path)?;
let name = source.as_local().unwrap().file_name().unwrap().to_str().unwrap();
let name = name.split('.').next().unwrap();
let name = name.replace('-', "_").replace(|c: char| c.is_whitespace(), "");
Ok(Self::new_raw(
syn::parse_str(&util::safe_identifier_name(name)).wrap_err_with(|| {
format!("failed convert file name to contract identifier {}", path)
})?,
source,
))
}
pub fn add_event_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
self.event_aliases.insert(signature.into(), alias.into());
self
}
pub fn add_method_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
self.method_aliases.insert(signature.into(), alias.into());
self
}
pub fn add_error_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
self.error_aliases.insert(signature.into(), alias.into());
self
}
#[deprecated = "Use add_derive instead"]
#[doc(hidden)]
pub fn add_event_derive<S: AsRef<str>>(self, derive: S) -> Result<Self> {
self.add_derive(derive)
}
pub fn add_derive<S: AsRef<str>>(mut self, derive: S) -> Result<Self> {
self.derives.push(syn::parse_str(derive.as_ref())?);
Ok(self)
}
#[deprecated = "Use format instead"]
#[doc(hidden)]
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
self.format = rustfmt;
self
}
pub fn format(mut self, format: bool) -> Self {
self.format = format;
self
}
pub fn emit_cargo_directives(mut self, emit_cargo_directives: bool) -> Self {
self.emit_cargo_directives = emit_cargo_directives;
self
}
pub fn generate(self) -> Result<ContractBindings> {
let format = self.format;
let emit = self.emit_cargo_directives;
let path = self.abi_source.as_local().cloned();
let name = self.contract_name.to_string();
let (expanded, _) = self.expand()?;
let path = if let (true, Some(path)) = (emit, &path) {
println!("cargo:rerun-if-changed={}", path.display());
None
} else {
path.as_deref()
};
Ok(ContractBindings { tokens: expanded.into_tokens_with_path(path), format, name })
}
pub fn expand(self) -> Result<(ExpandedContract, Context)> {
let ctx = Context::from_abigen(self)?;
Ok((ctx.expand()?, ctx))
}
}
impl Abigen {
pub fn source(&self) -> &Source {
&self.abi_source
}
pub fn source_mut(&mut self) -> &mut Source {
&mut self.abi_source
}
pub fn name(&self) -> &Ident {
&self.contract_name
}
pub fn name_mut(&mut self) -> &mut Ident {
&mut self.contract_name
}
pub fn method_aliases(&self) -> &HashMap<String, String> {
&self.method_aliases
}
pub fn method_aliases_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.method_aliases
}
pub fn event_aliases(&self) -> &HashMap<String, String> {
&self.event_aliases
}
pub fn error_aliases_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.error_aliases
}
pub fn derives(&self) -> &Vec<syn::Path> {
&self.derives
}
pub fn derives_mut(&mut self) -> &mut Vec<syn::Path> {
&mut self.derives
}
}
#[derive(Clone)]
pub struct ContractBindings {
pub name: String,
pub tokens: TokenStream,
pub format: bool,
}
impl ToTokens for ContractBindings {
fn into_token_stream(self) -> TokenStream {
self.tokens
}
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(std::iter::once(self.tokens.clone()))
}
fn to_token_stream(&self) -> TokenStream {
self.tokens.clone()
}
}
impl fmt::Display for ContractBindings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.format {
let syntax_tree = syn::parse2::<syn::File>(self.tokens.clone()).unwrap();
let s = prettyplease::unparse(&syntax_tree);
f.write_str(&s)
} else {
fmt::Display::fmt(&self.tokens, f)
}
}
}
impl fmt::Debug for ContractBindings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ContractBindings")
.field("name", &self.name)
.field("format", &self.format)
.finish()
}
}
impl ContractBindings {
pub fn to_vec(&self) -> Vec<u8> {
self.to_string().into_bytes()
}
pub fn write(&self, w: &mut impl io::Write) -> io::Result<()> {
let tokens = self.to_string();
w.write_all(tokens.as_bytes())
}
pub fn write_fmt(&self, w: &mut impl fmt::Write) -> fmt::Result {
let tokens = self.to_string();
w.write_str(&tokens)
}
pub fn write_to_file(&self, file: impl AsRef<Path>) -> io::Result<()> {
fs::write(file.as_ref(), self.to_string())
}
pub fn write_module_in_dir(&self, dir: impl AsRef<Path>) -> io::Result<()> {
let file = dir.as_ref().join(self.module_filename());
self.write_to_file(file)
}
#[deprecated = "Use ::quote::ToTokens::into_token_stream instead"]
#[doc(hidden)]
pub fn into_tokens(self) -> TokenStream {
self.tokens
}
pub fn module_name(&self) -> String {
util::safe_module_name(&self.name)
}
pub fn module_filename(&self) -> String {
let mut name = self.module_name();
name.push_str(".rs");
name
}
}
fn in_build_script() -> bool {
std::env::var("TARGET").is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_generate_structs() {
let greeter = include_str!("../../tests/solidity-contracts/greeter_with_struct.json");
let abigen = Abigen::new("Greeter", greeter).unwrap();
let gen = abigen.generate().unwrap();
let out = gen.tokens.to_string();
assert!(out.contains("pub struct Stuff"));
}
#[test]
fn can_generate_constructor_params() {
let contract = include_str!("../../tests/solidity-contracts/StructConstructor.json");
let abigen = Abigen::new("MyContract", contract).unwrap();
let gen = abigen.generate().unwrap();
let out = gen.tokens.to_string();
assert!(out.contains("pub struct ConstructorParams"));
}
#[test]
fn parse_empty_abigen() {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../tests/solidity-contracts/draft-IERCPermit.json");
let abigen = Abigen::from_file(path).unwrap();
assert_eq!(abigen.name().to_string(), "draft_IERCPermit");
}
}