refactor(abigen): keep and use parsed spans (#2247)

* order

* refactor(abigen): keep and use spans

* chore: use getters instead of making fields public

* fix: tests

* docs: update abigen documentation

* chore: clippy
This commit is contained in:
DaniPopes 2023-03-13 20:49:32 +01:00 committed by GitHub
parent d073930fa3
commit c9a7b4acaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 345 additions and 307 deletions

2
Cargo.lock generated
View File

@ -1382,11 +1382,9 @@ version = "2.0.0"
dependencies = [
"ethers-contract-abigen",
"ethers-core",
"eyre",
"hex",
"proc-macro2",
"quote",
"serde_json",
"syn",
]

View File

@ -19,7 +19,7 @@ ethers-etherscan = { version = "^2.0.0", path = "../../ethers-etherscan", defaul
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0.12", default-features = false, features = ["full"] }
syn = { version = "1.0.12", default-features = false, features = ["full", "extra-traits"] }
prettyplease = "0.1.23"
Inflector = "0.11"

View File

@ -120,7 +120,7 @@ pub struct Context {
}
impl Context {
/// Expands the whole rust contract
/// Generates the tokens.
pub fn expand(&self) -> Result<ExpandedContract> {
let name = &self.contract_ident;
let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
@ -232,8 +232,6 @@ impl Context {
}
};
let contract_ident = util::ident(&args.contract_name);
// 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.
@ -281,27 +279,20 @@ impl Context {
);
}
let extra_derives = args
.derives
.iter()
.map(|derive| syn::parse_str::<Path>(derive))
.collect::<Result<Vec<_>, _>>()
.wrap_err("failed to parse event derives")?;
Ok(Context {
Ok(Self {
abi,
human_readable,
abi_str: Literal::string(&abi_str),
abi_parser,
internal_structs,
contract_ident,
contract_name: args.contract_name,
contract_name: args.contract_name.to_string(),
contract_ident: args.contract_name,
contract_bytecode,
contract_deployed_bytecode,
method_aliases,
error_aliases: Default::default(),
extra_derives,
event_aliases,
extra_derives: args.derives,
})
}

View File

@ -36,7 +36,7 @@ pub use ethers_core::types::Address;
use contract::{Context, ExpandedContract};
use eyre::{Context as _, Result};
use proc_macro2::TokenStream;
use proc_macro2::{Ident, TokenStream};
use quote::ToTokens;
use std::{collections::HashMap, fmt, fs, io, path::Path};
@ -57,11 +57,10 @@ use std::{collections::HashMap, fmt, fs, io, path::Path};
/// which exports an `ERC20Token` struct, along with all its events.
///
/// ```no_run
/// # use ethers_contract_abigen::Abigen;
/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
/// use ethers_contract_abigen::Abigen;
///
/// Abigen::new("ERC20Token", "./abi.json")?.generate()?.write_to_file("token.rs")?;
/// # Ok(())
/// # }
/// # Ok::<_, Box<dyn std::error::Error>>(())
#[derive(Clone, Debug)]
#[must_use = "Abigen does nothing unless you generate or expand it."]
pub struct Abigen {
@ -69,31 +68,35 @@ pub struct Abigen {
abi_source: Source,
/// The contract's name to use for the generated type.
contract_name: String,
/// Manually specified contract method aliases.
method_aliases: HashMap<String, String>,
/// Manually specified `derive` macros added to all structs and enums.
derives: Vec<String>,
contract_name: Ident,
/// Whether to format the generated bindings using [`prettyplease`].
format: bool,
/// Manually specified contract method aliases.
method_aliases: HashMap<String, String>,
/// Manually specified event name aliases.
event_aliases: HashMap<String, String>,
/// Manually specified error name aliases.
error_aliases: HashMap<String, String>,
/// Manually specified `derive` macros added to all structs and enums.
derives: Vec<syn::Path>,
}
impl Abigen {
/// Creates a new builder with the given [ABI Source][Source].
pub fn new<T: Into<String>, S: AsRef<str>>(contract_name: T, abi_source: S) -> Result<Self> {
let abi_source = abi_source.as_ref().parse()?;
/// Creates a new builder with the given contract name and ABI source strings.
///
/// # Errors
///
/// If `contract_name` could not be parsed as a valid [Ident], or if `abi_source` could not be
/// parsed as a valid [Source].
pub fn new<T: AsRef<str>, S: AsRef<str>>(contract_name: T, abi_source: S) -> Result<Self> {
Ok(Self {
abi_source,
contract_name: contract_name.into(),
abi_source: abi_source.as_ref().parse()?,
contract_name: syn::parse_str(contract_name.as_ref())?,
format: true,
method_aliases: Default::default(),
derives: Default::default(),
@ -102,6 +105,19 @@ impl Abigen {
})
}
/// Creates a new builder with the given contract name [Ident] and [ABI source][Source].
pub fn new_raw(contract_name: Ident, abi_source: Source) -> Self {
Self {
contract_name,
abi_source,
format: true,
method_aliases: Default::default(),
derives: Default::default(),
event_aliases: Default::default(),
error_aliases: Default::default(),
}
}
/// Attempts to load a new builder from an ABI JSON file at the specific path.
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let path = dunce::canonicalize(path).wrap_err("File does not exist")?;
@ -155,6 +171,20 @@ impl Abigen {
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)
}
/// Add a custom derive to the derives for all structs and enums.
///
/// For example, this makes it possible to derive serde::Serialize and serde::Deserialize.
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 {
@ -171,25 +201,10 @@ impl Abigen {
self
}
#[deprecated = "Use add_derive instead"]
#[doc(hidden)]
pub fn add_event_derive<S: Into<String>>(mut self, derive: S) -> Self {
self.derives.push(derive.into());
self
}
/// Add a custom derive to the derives for all structs and enums.
///
/// For example, this makes it possible to derive serde::Serialize and serde::Deserialize.
pub fn add_derive<S: Into<String>>(mut self, derive: S) -> Self {
self.derives.push(derive.into());
self
}
/// Generates the contract bindings.
pub fn generate(self) -> Result<ContractBindings> {
let format = self.format;
let name = self.contract_name.clone();
let name = self.contract_name.to_string();
let (expanded, _) = self.expand()?;
Ok(ContractBindings { tokens: expanded.into_tokens(), format, name })
}
@ -202,7 +217,59 @@ impl Abigen {
}
}
/// Type-safe contract bindings generated by `Abigen`.
impl Abigen {
/// Returns a reference to the contract's ABI source.
pub fn source(&self) -> &Source {
&self.abi_source
}
/// Returns a mutable reference to the contract's ABI source.
pub fn source_mut(&mut self) -> &mut Source {
&mut self.abi_source
}
/// Returns a reference to the contract's name.
pub fn name(&self) -> &Ident {
&self.contract_name
}
/// Returns a mutable reference to the contract's name.
pub fn name_mut(&mut self) -> &mut Ident {
&mut self.contract_name
}
/// Returns a reference to the contract's method aliases.
pub fn method_aliases(&self) -> &HashMap<String, String> {
&self.method_aliases
}
/// Returns a mutable reference to the contract's method aliases.
pub fn method_aliases_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.method_aliases
}
/// Returns a reference to the contract's event aliases.
pub fn event_aliases(&self) -> &HashMap<String, String> {
&self.event_aliases
}
/// Returns a mutable reference to the contract's event aliases.
pub fn error_aliases_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.error_aliases
}
/// Returns a reference to the contract's derives.
pub fn derives(&self) -> &Vec<syn::Path> {
&self.derives
}
/// Returns a mutable reference to the contract's derives.
pub fn derives_mut(&mut self) -> &mut Vec<syn::Path> {
&mut self.derives
}
}
/// Type-safe contract bindings generated by [Abigen].
///
/// This type can be either written to file or converted to a token stream for a procedural macro.
#[derive(Clone)]

View File

@ -129,7 +129,7 @@ impl MultiAbigen {
/// Removes all `Abigen` items that should not be included based on the given filter
pub fn apply_filter(&mut self, filter: &ContractFilter) {
self.abigens.retain(|abi| filter.is_match(&abi.contract_name))
self.abigens.retain(|abi| filter.is_match(abi.contract_name.to_string()))
}
/// Add another Abigen to the module or lib

View File

@ -24,9 +24,7 @@ proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0.12"
serde_json = "1.0.53"
hex = { version = "0.4.3", default-features = false, features = ["std"] }
eyre = "0.6"
[package.metadata.docs.rs]
all-features = true

View File

@ -1,57 +1,47 @@
//! Implementation of procedural macro for generating type-safe bindings to an Ethereum smart
//! contract.
use crate::spanned::{ParseInner, Spanned};
use ethers_contract_abigen::{
contract::{Context, ExpandedContract},
multi::MultiExpansion,
Abigen,
};
use ethers_core::abi::{Function, FunctionExt, Param, StateMutability};
use eyre::Result;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::ToTokens;
use crate::spanned::Spanned;
use ethers_contract_abigen::{multi::MultiExpansion, Abigen};
use proc_macro2::TokenStream;
use std::collections::HashSet;
use syn::{
braced,
ext::IdentExt,
parenthesized,
parse::{Error, Parse, ParseStream, Result as ParseResult},
parse::{Error, Parse, ParseStream, Result},
punctuated::Punctuated,
Ident, LitStr, Path, Token,
};
/// A series of `ContractArgs` separated by `;`
#[derive(Clone, Debug)]
pub(crate) struct Contracts {
pub(crate) inner: Vec<(Span, ContractArgs)>,
pub(crate) inner: Vec<ContractArgs>,
}
impl Contracts {
pub(crate) fn expand(self) -> Result<TokenStream2, Error> {
pub(crate) fn expand(self) -> Result<TokenStream> {
let mut expansions = Vec::with_capacity(self.inner.len());
// expand all contracts
for (span, contract) in self.inner {
let contract =
Self::expand_contract(contract).map_err(|err| Error::new(span, err.to_string()))?;
for contract in self.inner {
let span = contract.abi.span();
let contract = contract
.into_builder()
.and_then(|a| a.expand().map_err(|e| Error::new(span, e)))?;
expansions.push(contract);
}
// expand all contract expansions
Ok(MultiExpansion::new(expansions).expand_inplace())
}
fn expand_contract(contract: ContractArgs) -> Result<(ExpandedContract, Context)> {
contract.into_builder()?.expand()
}
}
impl Parse for Contracts {
fn parse(input: ParseStream) -> ParseResult<Self> {
let inner = input
.parse_terminated::<_, Token![;]>(ContractArgs::spanned_parse)?
.into_iter()
.collect();
fn parse(input: ParseStream) -> Result<Self> {
let inner =
input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect();
Ok(Self { inner })
}
}
@ -59,71 +49,62 @@ impl Parse for Contracts {
/// Contract procedural macro arguments.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ContractArgs {
name: String,
abi: String,
parameters: Vec<Parameter>,
name: Ident,
abi: LitStr,
parameters: Punctuated<Parameter, Token![,]>,
}
impl ContractArgs {
fn into_builder(self) -> Result<Abigen> {
let mut builder = Abigen::new(&self.name, &self.abi)?;
// use the name's ident
let contract_name = self.name;
let abi = self.abi.value();
let abi_source = abi.parse().map_err(|e| Error::new(self.abi.span(), e))?;
let mut builder = Abigen::new_raw(contract_name, abi_source);
for parameter in self.parameters.into_iter() {
builder = match parameter {
Parameter::Methods(methods) => methods
.into_iter()
.fold(builder, |builder, m| builder.add_method_alias(m.signature, m.alias)),
Parameter::Derives(derives) => {
derives.into_iter().fold(builder, |builder, derive| builder.add_derive(derive))
}
};
for parameter in self.parameters {
match parameter {
Parameter::Methods(methods) => builder
.method_aliases_mut()
.extend(methods.into_iter().map(|m| (m.signature, m.alias.to_string()))),
Parameter::Derives(derives) => builder.derives_mut().extend(derives),
}
}
Ok(builder)
}
}
impl ParseInner for ContractArgs {
fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> {
// read the contract name
let name = input.parse::<Ident>()?.to_string();
impl Parse for ContractArgs {
fn parse(input: ParseStream) -> Result<Self> {
// name
let name = input.parse::<Ident>()?;
// skip the comma
input.parse::<Token![,]>()?;
// abi
// TODO(nlordell): Due to limitation with the proc-macro Span API, we
// can't currently get a path the the file where we were called from;
// therefore, the path will always be rooted on the cargo manifest
// directory. Eventually we can use the `Span::source_file` API to
// have a better experience.
let (span, abi) = {
let literal = input.parse::<LitStr>()?;
(literal.span(), literal.value())
};
let mut parameters = Vec::new();
let lookahead = input.lookahead1();
if lookahead.peek(Token![,]) {
input.parse::<Token![,]>()?;
let abi = input.parse::<LitStr>()?;
// optional parameters
let mut parameters = Punctuated::default();
if input.parse::<Token![,]>().is_ok() {
loop {
if input.is_empty() {
if input.is_empty() || input.peek(Token![;]) {
break
}
let lookahead = input.lookahead1();
if lookahead.peek(Token![;]) {
break
}
let param = Parameter::parse(input)?;
parameters.push(param);
let lookahead = input.lookahead1();
if lookahead.peek(Token![,]) {
input.parse::<Token![,]>()?;
parameters.push_value(input.parse()?);
if let Ok(comma) = input.parse() {
parameters.push_punct(comma);
}
}
}
Ok((span, ContractArgs { name, abi, parameters }))
Ok(ContractArgs { name, abi, parameters })
}
}
@ -131,60 +112,40 @@ impl ParseInner for ContractArgs {
#[derive(Clone, Debug, PartialEq, Eq)]
enum Parameter {
Methods(Vec<Method>),
Derives(Vec<String>),
Derives(Punctuated<Path, Token![,]>),
}
impl Parse for Parameter {
fn parse(input: ParseStream) -> ParseResult<Self> {
let name = input.call(Ident::parse_any)?;
let param = match name.to_string().as_str() {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
match name.to_string().as_str() {
"methods" => {
let content;
braced!(content in input);
let methods = {
let parsed =
content.parse_terminated::<_, Token![;]>(Spanned::<Method>::parse)?;
let parsed = content.parse_terminated::<_, Token![;]>(Spanned::<Method>::parse)?;
let mut methods = Vec::with_capacity(parsed.len());
let mut signatures = HashSet::new();
let mut aliases = HashSet::new();
for method in parsed {
if !signatures.insert(method.signature.clone()) {
return Err(Error::new(
method.span(),
"duplicate method signature in `abigen!` macro invocation",
))
}
if !aliases.insert(method.alias.clone()) {
return Err(Error::new(
method.span(),
"duplicate method alias in `abigen!` macro invocation",
))
}
methods.push(method.into_inner())
let mut methods = Vec::with_capacity(parsed.len());
let mut signatures = HashSet::new();
let mut aliases = HashSet::new();
for method in parsed {
if !signatures.insert(method.signature.clone()) {
return Err(Error::new(method.span(), "duplicate method signature"))
}
methods
};
Parameter::Methods(methods)
if !aliases.insert(method.alias.clone()) {
return Err(Error::new(method.alias.span(), "duplicate method alias"))
}
methods.push(method.into_inner());
}
Ok(Parameter::Methods(methods))
}
"derives" | "event_derives" => {
let content;
parenthesized!(content in input);
let derives = content
.parse_terminated::<_, Token![,]>(Path::parse)?
.into_iter()
.map(|path| path.to_token_stream().to_string())
.collect();
Parameter::Derives(derives)
let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?;
Ok(Parameter::Derives(derives))
}
_ => {
return Err(Error::new(name.span(), format!("unexpected named parameter `{name}`")))
}
};
Ok(param)
_ => Err(Error::new(name.span(), "unexpected named parameter")),
}
}
}
@ -192,44 +153,40 @@ impl Parse for Parameter {
#[derive(Clone, Debug, PartialEq, Eq)]
struct Method {
signature: String,
alias: String,
alias: Ident,
}
impl Parse for Method {
fn parse(input: ParseStream) -> ParseResult<Self> {
let function = {
let name = input.parse::<Ident>()?.to_string();
fn parse(input: ParseStream) -> Result<Self> {
// `{name}({params.join(",")})`
let mut signature = String::with_capacity(64);
let content;
parenthesized!(content in input);
let inputs = content
.parse_terminated::<_, Token![,]>(Ident::parse)?
.iter()
.map(|ident| {
let kind = serde_json::from_value(serde_json::json!(&ident.to_string()))
.map_err(|err| Error::new(ident.span(), err))?;
Ok(Param { name: "".into(), kind, internal_type: None })
})
.collect::<ParseResult<Vec<_>>>()?;
// function name
let name = input.parse::<Ident>()?;
signature.push_str(&name.to_string());
#[allow(deprecated)]
Function {
name,
inputs,
// function params
let content;
parenthesized!(content in input);
let params = content.parse_terminated::<_, Token![,]>(Ident::parse)?;
let last_i = params.len().saturating_sub(1);
// NOTE: The output types and const-ness of the function do not
// affect its signature.
outputs: vec![],
state_mutability: StateMutability::NonPayable,
constant: None,
signature.push('(');
for (i, param) in params.into_iter().enumerate() {
let s = param.to_string();
// validate
ethers_core::abi::ethabi::param_type::Reader::read(&s)
.map_err(|e| Error::new(param.span(), e))?;
signature.push_str(&s);
if i < last_i {
signature.push(',');
}
};
let signature = function.abi_signature();
}
signature.push(')');
input.parse::<Token![as]>()?;
let alias = {
let ident = input.parse::<Ident>()?;
ident.to_string()
};
let alias = input.parse()?;
Ok(Method { signature, alias })
}
@ -238,43 +195,80 @@ impl Parse for Method {
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
use quote::quote;
use syn::parse::Parser;
macro_rules! contract_args_result {
($($arg:tt)*) => {{
use syn::parse::Parser;
<Spanned<ContractArgs> as Parse>::parse
.parse2(quote::quote! { $($arg)* })
($($tt:tt)+) => {{
Parser::parse2(Contracts::parse, quote!($($tt)+))
}};
}
macro_rules! contract_args {
($($arg:tt)*) => {
contract_args_result!($($arg)*)
($($tt:tt)*) => {
contract_args_result!($($tt)*)
.expect("failed to parse contract args")
.into_inner()
.inner
};
}
macro_rules! contract_args_err {
($($arg:tt)*) => {
contract_args_result!($($arg)*)
($($tt:tt)*) => {
contract_args_result!($($tt)*)
.expect_err("expected parse contract args to error")
};
}
#[allow(unused)]
fn method(signature: &str, alias: &str) -> Method {
Method { signature: signature.into(), alias: alias.into() }
Method { signature: signature.into(), alias: ident(alias) }
}
fn parse_contracts(s: TokenStream2) -> Vec<ContractArgs> {
use syn::parse::Parser;
Contracts::parse.parse2(s).unwrap().inner.into_iter().map(|(_, c)| c).collect::<Vec<_>>()
// Note: AST structs implement PartialEq by comparing the string repr, so the span is ignored.
fn arg(
name: &str,
abi: &str,
parameters: impl IntoIterator<Item = Parameter>,
trailing: bool,
) -> ContractArgs {
ContractArgs {
name: ident(name),
abi: lit_str(abi),
parameters: params(parameters, trailing),
}
}
fn ident(s: &str) -> Ident {
Ident::new(s, Span::call_site())
}
fn lit_str(s: &str) -> LitStr {
LitStr::new(s, Span::call_site())
}
fn params(
v: impl IntoIterator<Item = Parameter>,
trailing: bool,
) -> Punctuated<Parameter, Token![,]> {
let mut punct: Punctuated<Parameter, Token![,]> = v.into_iter().collect();
if trailing {
punct.push_punct(Default::default());
}
punct
}
fn derives<'a>(v: impl IntoIterator<Item = &'a str>, trailing: bool) -> Parameter {
let mut derives: Punctuated<_, _> =
v.into_iter().map(|s| syn::parse_str::<syn::Path>(s).unwrap()).collect();
if trailing {
derives.push_punct(Default::default());
}
Parameter::Derives(derives)
}
#[test]
fn parse_multi_contract_args_events() {
let args = parse_contracts(quote::quote! {
let args = contract_args! {
TestContract,
"path/to/abi.json",
event_derives(serde::Deserialize, serde::Serialize);
@ -282,96 +276,85 @@ mod tests {
TestContract2,
"other.json",
event_derives(serde::Deserialize, serde::Serialize);
});
};
assert_eq!(
args,
vec![
ContractArgs {
name: "TestContract".to_string(),
abi: "path/to/abi.json".to_string(),
parameters: vec![Parameter::Derives(vec![
"serde :: Deserialize".into(),
"serde :: Serialize".into(),
])],
},
ContractArgs {
name: "TestContract2".to_string(),
abi: "other.json".to_string(),
parameters: vec![Parameter::Derives(vec![
"serde :: Deserialize".into(),
"serde :: Serialize".into(),
])],
},
arg(
"TestContract",
"path/to/abi.json",
[derives(["serde::Deserialize", "serde::Serialize"], false)],
false
),
arg(
"TestContract2",
"other.json",
[derives(["serde::Deserialize", "serde::Serialize"], false)],
false
),
]
);
}
#[test]
fn parse_multi_contract_args_methods() {
let args = parse_contracts(quote::quote! {
let args = contract_args! {
TestContract,
"path/to/abi.json",
methods {
methods {
myMethod(uint256, bool) as my_renamed_method;
myOtherMethod() as my_other_renamed_method;
}
;
};
TestContract2,
"other.json",
event_derives(serde::Deserialize, serde::Serialize);
});
};
assert_eq!(
args,
vec![
ContractArgs {
name: "TestContract".to_string(),
abi: "path/to/abi.json".to_string(),
parameters: vec![Parameter::Methods(vec![
arg(
"TestContract",
"path/to/abi.json",
[Parameter::Methods(vec![
method("myMethod(uint256,bool)", "my_renamed_method"),
method("myOtherMethod()", "my_other_renamed_method"),
])],
},
ContractArgs {
name: "TestContract2".to_string(),
abi: "other.json".to_string(),
parameters: vec![Parameter::Derives(vec![
"serde :: Deserialize".into(),
"serde :: Serialize".into(),
])],
},
false
),
arg(
"TestContract2",
"other.json",
[derives(["serde::Deserialize", "serde::Serialize"], false)],
false
),
]
);
}
#[test]
fn parse_multi_contract_args() {
let args = parse_contracts(quote::quote! {
let args = contract_args! {
TestContract,
"path/to/abi.json",;
TestContract2,
"other.json",
event_derives(serde::Deserialize, serde::Serialize);
});
event_derives(serde::Deserialize, serde::Serialize,);
};
assert_eq!(
args,
vec![
ContractArgs {
name: "TestContract".to_string(),
abi: "path/to/abi.json".to_string(),
parameters: vec![],
},
ContractArgs {
name: "TestContract2".to_string(),
abi: "other.json".to_string(),
parameters: vec![Parameter::Derives(vec![
"serde :: Deserialize".into(),
"serde :: Serialize".into(),
])],
},
arg("TestContract", "path/to/abi.json", [], false),
arg(
"TestContract2",
"other.json",
[derives(["serde::Deserialize", "serde::Serialize"], true)],
false
),
]
);
}
@ -379,21 +362,13 @@ mod tests {
#[test]
fn parse_contract_args() {
let args = contract_args!(TestContract, "path/to/abi.json");
assert_eq!(args.name, "TestContract");
assert_eq!(args.abi, "path/to/abi.json");
assert_eq!(*args.first().unwrap(), arg("TestContract", "path/to/abi.json", [], false));
}
#[test]
fn parse_contract_args_with_defaults() {
let args = contract_args!(TestContract, "[{}]");
assert_eq!(
args,
ContractArgs {
name: "TestContract".to_string(),
abi: "[{}]".to_string(),
parameters: vec![],
},
);
assert_eq!(*args.first().unwrap(), arg("TestContract", "[{}]", [], false));
}
#[test]
@ -408,23 +383,19 @@ mod tests {
event_derives (Asdf, a::B, a::b::c::D)
);
assert_eq!(
args,
ContractArgs {
name: "TestContract".to_string(),
abi: "abi.json".to_string(),
parameters: vec![
// Parameter::Contract("Contract".into()),
*args.first().unwrap(),
arg(
"TestContract",
"abi.json",
[
Parameter::Methods(vec![
method("myMethod(uint256,bool)", "my_renamed_method"),
method("myOtherMethod()", "my_other_renamed_method"),
]),
Parameter::Derives(vec![
"Asdf".into(),
"a :: B".into(),
"a :: b :: c :: D".into()
])
derives(["Asdf", "a::B", "a::b::c::D"], false)
],
},
false
)
);
}

View File

@ -1,13 +1,12 @@
//! Implementation of procedural macro for generating type-safe bindings to an
//! ethereum smart contract.
//! Procedural macros for generating type-safe bindings to an Ethereum smart contract.
#![deny(missing_docs, unsafe_code, unused_crate_dependencies)]
#![deny(rustdoc::broken_intra_doc_links)]
use abigen::Contracts;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use abigen::Contracts;
pub(crate) mod abi_ty;
mod abigen;
mod call;
@ -19,18 +18,42 @@ mod event;
mod spanned;
pub(crate) mod utils;
/// Proc macro to generate type-safe bindings to a contract(s). This macro
/// accepts one or more Ethereum contract ABI or a path. Note that relative paths are
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
/// Environment variable interpolation is supported via `$` prefix, like
/// `"$CARGO_MANIFEST_DIR/contracts/c.json"`
/// Generates type-safe bindings to an Ethereum smart contract from its ABI.
///
/// All the accepted ABI sources are listed in the examples below and in [Source].
///
/// Note:
/// - relative paths are rooted in the crate's root (`CARGO_MANIFEST_DIR`).
/// - Environment variable interpolation is supported via `$` prefix, like
/// `"$CARGO_MANIFEST_DIR/contracts/c.json"`
/// - Etherscan rate-limits requests to their API. To avoid this, set the `ETHERSCAN_API_KEY`
/// environment variable.
///
/// Additionally, this macro accepts additional parameters to configure some aspects of the code
/// generation:
/// - `methods`: A list of mappings from method signatures to method names allowing methods names to
/// be explicitely set for contract methods. This also provides a workaround for generating code
/// for contracts with multiple methods with the same name.
/// - `derives`: A list of additional derive macros that are added to all the generated structs and
/// enums, after the default ones which are ([when applicable][tuple_derive_ref]):
/// * [PartialEq]
/// * [Eq]
/// * [Debug]
/// * [Default]
/// * [Hash]
///
/// [Source]: ethers_contract_abigen::Source
/// [tuple_derive_ref]: https://doc.rust-lang.org/stable/std/primitive.tuple.html#trait-implementations-1
///
/// # Examples
///
/// All the possible ABI sources:
///
/// ```ignore
/// # use ethers_contract_derive::abigen;
/// use ethers_contract_derive::abigen;
///
/// // ABI Path
/// abigen!(MyContract, "MyContract.json");
/// abigen!(MyContract, "./MyContractABI.json");
///
/// // HTTP(S) source
/// abigen!(MyContract, "https://my.domain.local/path/to/contract.json");
@ -50,19 +73,7 @@ pub(crate) mod utils;
/// ]");
/// ```
///
/// Note that Etherscan rate-limits requests to their API, to avoid this an
/// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
/// that API key when retrieving the contract ABI.
///
/// Currently, the proc macro accepts additional parameters to configure some
/// aspects of the code generation. Specifically it accepts:
/// - `methods`: A list of mappings from method signatures to method names allowing methods names to
/// be explicitely set for contract methods. This also provides a workaround for generating code
/// for contracts with multiple methods with the same name.
/// - `event_derives`: A list of additional derives that should be added to contract event structs
/// and enums.
///
/// # Example
/// Specify additional parameters:
///
/// ```ignore
/// abigen!(
@ -71,7 +82,7 @@ pub(crate) mod utils;
/// methods {
/// myMethod(uint256,bool) as my_renamed_method;
/// },
/// event_derives (serde::Deserialize, serde::Serialize),
/// derives(serde::Deserialize, serde::Serialize),
/// );
/// ```
///
@ -83,7 +94,6 @@ pub(crate) mod utils;
/// `abigen!` bundles all type duplicates so that all rust contracts also use
/// the same rust types.
///
/// # Example Multiple contracts
/// ```ignore
/// abigen!(
/// MyContract,
@ -91,18 +101,21 @@ pub(crate) mod utils;
/// methods {
/// myMethod(uint256,bool) as my_renamed_method;
/// },
/// event_derives (serde::Deserialize, serde::Serialize);
/// derives(serde::Deserialize, serde::Serialize);
///
/// MyOtherContract,
/// "path/to/MyOtherContract.json",
/// event_derives (serde::Deserialize, serde::Serialize);
/// derives(serde::Deserialize, serde::Serialize);
/// );
/// ```
#[proc_macro]
pub fn abigen(input: TokenStream) -> TokenStream {
let contracts = parse_macro_input!(input as Contracts);
contracts.expand().unwrap_or_else(|err| err.to_compile_error()).into()
match contracts.expand() {
Ok(tokens) => tokens,
Err(err) => err.to_compile_error(),
}
.into()
}
/// Derives the `AbiType` and all `Tokenizable` traits for the labeled type.
@ -146,7 +159,7 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
#[proc_macro_derive(EthAbiCodec)]
pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
TokenStream::from(codec::derive_codec_impl(&input))
codec::derive_codec_impl(&input).into()
}
/// Derives `fmt::Display` trait and generates a convenient format for all the