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:
parent
d073930fa3
commit
c9a7b4acaf
|
@ -1382,11 +1382,9 @@ version = "2.0.0"
|
|||
dependencies = [
|
||||
"ethers-contract-abigen",
|
||||
"ethers-core",
|
||||
"eyre",
|
||||
"hex",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue