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 = [
|
dependencies = [
|
||||||
"ethers-contract-abigen",
|
"ethers-contract-abigen",
|
||||||
"ethers-core",
|
"ethers-core",
|
||||||
"eyre",
|
|
||||||
"hex",
|
"hex",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_json",
|
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ ethers-etherscan = { version = "^2.0.0", path = "../../ethers-etherscan", defaul
|
||||||
|
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "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"
|
prettyplease = "0.1.23"
|
||||||
|
|
||||||
Inflector = "0.11"
|
Inflector = "0.11"
|
||||||
|
|
|
@ -120,7 +120,7 @@ pub struct Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Expands the whole rust contract
|
/// Generates the tokens.
|
||||||
pub fn expand(&self) -> Result<ExpandedContract> {
|
pub fn expand(&self) -> Result<ExpandedContract> {
|
||||||
let name = &self.contract_ident;
|
let name = &self.contract_ident;
|
||||||
let name_mod = util::ident(&util::safe_module_name(&self.contract_name));
|
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
|
// NOTE: We only check for duplicate signatures here, since if there are
|
||||||
// duplicate aliases, the compiler will produce a warning because a
|
// duplicate aliases, the compiler will produce a warning because a
|
||||||
// method will be re-defined.
|
// method will be re-defined.
|
||||||
|
@ -281,27 +279,20 @@ impl Context {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let extra_derives = args
|
Ok(Self {
|
||||||
.derives
|
|
||||||
.iter()
|
|
||||||
.map(|derive| syn::parse_str::<Path>(derive))
|
|
||||||
.collect::<Result<Vec<_>, _>>()
|
|
||||||
.wrap_err("failed to parse event derives")?;
|
|
||||||
|
|
||||||
Ok(Context {
|
|
||||||
abi,
|
abi,
|
||||||
human_readable,
|
human_readable,
|
||||||
abi_str: Literal::string(&abi_str),
|
abi_str: Literal::string(&abi_str),
|
||||||
abi_parser,
|
abi_parser,
|
||||||
internal_structs,
|
internal_structs,
|
||||||
contract_ident,
|
contract_name: args.contract_name.to_string(),
|
||||||
contract_name: args.contract_name,
|
contract_ident: args.contract_name,
|
||||||
contract_bytecode,
|
contract_bytecode,
|
||||||
contract_deployed_bytecode,
|
contract_deployed_bytecode,
|
||||||
method_aliases,
|
method_aliases,
|
||||||
error_aliases: Default::default(),
|
error_aliases: Default::default(),
|
||||||
extra_derives,
|
|
||||||
event_aliases,
|
event_aliases,
|
||||||
|
extra_derives: args.derives,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub use ethers_core::types::Address;
|
||||||
|
|
||||||
use contract::{Context, ExpandedContract};
|
use contract::{Context, ExpandedContract};
|
||||||
use eyre::{Context as _, Result};
|
use eyre::{Context as _, Result};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use std::{collections::HashMap, fmt, fs, io, path::Path};
|
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.
|
/// which exports an `ERC20Token` struct, along with all its events.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # use ethers_contract_abigen::Abigen;
|
/// use ethers_contract_abigen::Abigen;
|
||||||
/// # fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
///
|
||||||
/// Abigen::new("ERC20Token", "./abi.json")?.generate()?.write_to_file("token.rs")?;
|
/// Abigen::new("ERC20Token", "./abi.json")?.generate()?.write_to_file("token.rs")?;
|
||||||
/// # Ok(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// # }
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[must_use = "Abigen does nothing unless you generate or expand it."]
|
#[must_use = "Abigen does nothing unless you generate or expand it."]
|
||||||
pub struct Abigen {
|
pub struct Abigen {
|
||||||
|
@ -69,31 +68,35 @@ pub struct Abigen {
|
||||||
abi_source: Source,
|
abi_source: Source,
|
||||||
|
|
||||||
/// The contract's name to use for the generated type.
|
/// The contract's name to use for the generated type.
|
||||||
contract_name: String,
|
contract_name: Ident,
|
||||||
|
|
||||||
/// Manually specified contract method aliases.
|
|
||||||
method_aliases: HashMap<String, String>,
|
|
||||||
|
|
||||||
/// Manually specified `derive` macros added to all structs and enums.
|
|
||||||
derives: Vec<String>,
|
|
||||||
|
|
||||||
/// Whether to format the generated bindings using [`prettyplease`].
|
/// Whether to format the generated bindings using [`prettyplease`].
|
||||||
format: bool,
|
format: bool,
|
||||||
|
|
||||||
|
/// Manually specified contract method aliases.
|
||||||
|
method_aliases: HashMap<String, String>,
|
||||||
|
|
||||||
/// Manually specified event name aliases.
|
/// Manually specified event name aliases.
|
||||||
event_aliases: HashMap<String, String>,
|
event_aliases: HashMap<String, String>,
|
||||||
|
|
||||||
/// Manually specified error name aliases.
|
/// Manually specified error name aliases.
|
||||||
error_aliases: HashMap<String, String>,
|
error_aliases: HashMap<String, String>,
|
||||||
|
|
||||||
|
/// Manually specified `derive` macros added to all structs and enums.
|
||||||
|
derives: Vec<syn::Path>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Abigen {
|
impl Abigen {
|
||||||
/// Creates a new builder with the given [ABI Source][Source].
|
/// Creates a new builder with the given contract name and ABI source strings.
|
||||||
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()?;
|
/// # 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 {
|
Ok(Self {
|
||||||
abi_source,
|
abi_source: abi_source.as_ref().parse()?,
|
||||||
contract_name: contract_name.into(),
|
contract_name: syn::parse_str(contract_name.as_ref())?,
|
||||||
format: true,
|
format: true,
|
||||||
method_aliases: Default::default(),
|
method_aliases: Default::default(),
|
||||||
derives: 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.
|
/// 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> {
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
let path = dunce::canonicalize(path).wrap_err("File does not exist")?;
|
let path = dunce::canonicalize(path).wrap_err("File does not exist")?;
|
||||||
|
@ -155,6 +171,20 @@ impl Abigen {
|
||||||
self
|
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"]
|
#[deprecated = "Use format instead"]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
pub fn rustfmt(mut self, rustfmt: bool) -> Self {
|
||||||
|
@ -171,25 +201,10 @@ impl Abigen {
|
||||||
self
|
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.
|
/// Generates the contract bindings.
|
||||||
pub fn generate(self) -> Result<ContractBindings> {
|
pub fn generate(self) -> Result<ContractBindings> {
|
||||||
let format = self.format;
|
let format = self.format;
|
||||||
let name = self.contract_name.clone();
|
let name = self.contract_name.to_string();
|
||||||
let (expanded, _) = self.expand()?;
|
let (expanded, _) = self.expand()?;
|
||||||
Ok(ContractBindings { tokens: expanded.into_tokens(), format, name })
|
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.
|
/// This type can be either written to file or converted to a token stream for a procedural macro.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -129,7 +129,7 @@ impl MultiAbigen {
|
||||||
|
|
||||||
/// Removes all `Abigen` items that should not be included based on the given filter
|
/// Removes all `Abigen` items that should not be included based on the given filter
|
||||||
pub fn apply_filter(&mut self, filter: &ContractFilter) {
|
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
|
/// Add another Abigen to the module or lib
|
||||||
|
|
|
@ -24,9 +24,7 @@ proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "1.0.12"
|
syn = "1.0.12"
|
||||||
|
|
||||||
serde_json = "1.0.53"
|
|
||||||
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
hex = { version = "0.4.3", default-features = false, features = ["std"] }
|
||||||
eyre = "0.6"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
@ -1,57 +1,47 @@
|
||||||
//! Implementation of procedural macro for generating type-safe bindings to an Ethereum smart
|
//! Implementation of procedural macro for generating type-safe bindings to an Ethereum smart
|
||||||
//! contract.
|
//! contract.
|
||||||
|
|
||||||
use crate::spanned::{ParseInner, Spanned};
|
use crate::spanned::Spanned;
|
||||||
use ethers_contract_abigen::{
|
use ethers_contract_abigen::{multi::MultiExpansion, Abigen};
|
||||||
contract::{Context, ExpandedContract},
|
use proc_macro2::TokenStream;
|
||||||
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 std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syn::{
|
use syn::{
|
||||||
braced,
|
braced,
|
||||||
ext::IdentExt,
|
ext::IdentExt,
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Error, Parse, ParseStream, Result as ParseResult},
|
parse::{Error, Parse, ParseStream, Result},
|
||||||
|
punctuated::Punctuated,
|
||||||
Ident, LitStr, Path, Token,
|
Ident, LitStr, Path, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A series of `ContractArgs` separated by `;`
|
/// A series of `ContractArgs` separated by `;`
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Contracts {
|
pub(crate) struct Contracts {
|
||||||
pub(crate) inner: Vec<(Span, ContractArgs)>,
|
pub(crate) inner: Vec<ContractArgs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Contracts {
|
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());
|
let mut expansions = Vec::with_capacity(self.inner.len());
|
||||||
|
|
||||||
// expand all contracts
|
// expand all contracts
|
||||||
for (span, contract) in self.inner {
|
for contract in self.inner {
|
||||||
let contract =
|
let span = contract.abi.span();
|
||||||
Self::expand_contract(contract).map_err(|err| Error::new(span, err.to_string()))?;
|
let contract = contract
|
||||||
|
.into_builder()
|
||||||
|
.and_then(|a| a.expand().map_err(|e| Error::new(span, e)))?;
|
||||||
expansions.push(contract);
|
expansions.push(contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
// expand all contract expansions
|
// expand all contract expansions
|
||||||
Ok(MultiExpansion::new(expansions).expand_inplace())
|
Ok(MultiExpansion::new(expansions).expand_inplace())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_contract(contract: ContractArgs) -> Result<(ExpandedContract, Context)> {
|
|
||||||
contract.into_builder()?.expand()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Contracts {
|
impl Parse for Contracts {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let inner = input
|
let inner =
|
||||||
.parse_terminated::<_, Token![;]>(ContractArgs::spanned_parse)?
|
input.parse_terminated::<_, Token![;]>(ContractArgs::parse)?.into_iter().collect();
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
Ok(Self { inner })
|
Ok(Self { inner })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,71 +49,62 @@ impl Parse for Contracts {
|
||||||
/// Contract procedural macro arguments.
|
/// Contract procedural macro arguments.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct ContractArgs {
|
pub(crate) struct ContractArgs {
|
||||||
name: String,
|
name: Ident,
|
||||||
abi: String,
|
abi: LitStr,
|
||||||
parameters: Vec<Parameter>,
|
parameters: Punctuated<Parameter, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContractArgs {
|
impl ContractArgs {
|
||||||
fn into_builder(self) -> Result<Abigen> {
|
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() {
|
for parameter in self.parameters {
|
||||||
builder = match parameter {
|
match parameter {
|
||||||
Parameter::Methods(methods) => methods
|
Parameter::Methods(methods) => builder
|
||||||
.into_iter()
|
.method_aliases_mut()
|
||||||
.fold(builder, |builder, m| builder.add_method_alias(m.signature, m.alias)),
|
.extend(methods.into_iter().map(|m| (m.signature, m.alias.to_string()))),
|
||||||
Parameter::Derives(derives) => {
|
Parameter::Derives(derives) => builder.derives_mut().extend(derives),
|
||||||
derives.into_iter().fold(builder, |builder, derive| builder.add_derive(derive))
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(builder)
|
Ok(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseInner for ContractArgs {
|
impl Parse for ContractArgs {
|
||||||
fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
// read the contract name
|
// name
|
||||||
let name = input.parse::<Ident>()?.to_string();
|
let name = input.parse::<Ident>()?;
|
||||||
|
|
||||||
// skip the comma
|
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
|
// abi
|
||||||
// TODO(nlordell): Due to limitation with the proc-macro Span API, we
|
// 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;
|
// 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
|
// therefore, the path will always be rooted on the cargo manifest
|
||||||
// directory. Eventually we can use the `Span::source_file` API to
|
// directory. Eventually we can use the `Span::source_file` API to
|
||||||
// have a better experience.
|
// have a better experience.
|
||||||
let (span, abi) = {
|
let abi = input.parse::<LitStr>()?;
|
||||||
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![,]>()?;
|
|
||||||
|
|
||||||
|
// optional parameters
|
||||||
|
let mut parameters = Punctuated::default();
|
||||||
|
if input.parse::<Token![,]>().is_ok() {
|
||||||
loop {
|
loop {
|
||||||
if input.is_empty() {
|
if input.is_empty() || input.peek(Token![;]) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let lookahead = input.lookahead1();
|
parameters.push_value(input.parse()?);
|
||||||
if lookahead.peek(Token![;]) {
|
if let Ok(comma) = input.parse() {
|
||||||
break
|
parameters.push_punct(comma);
|
||||||
}
|
|
||||||
let param = Parameter::parse(input)?;
|
|
||||||
parameters.push(param);
|
|
||||||
let lookahead = input.lookahead1();
|
|
||||||
if lookahead.peek(Token![,]) {
|
|
||||||
input.parse::<Token![,]>()?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((span, ContractArgs { name, abi, parameters }))
|
Ok(ContractArgs { name, abi, parameters })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,60 +112,40 @@ impl ParseInner for ContractArgs {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
enum Parameter {
|
enum Parameter {
|
||||||
Methods(Vec<Method>),
|
Methods(Vec<Method>),
|
||||||
Derives(Vec<String>),
|
Derives(Punctuated<Path, Token![,]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Parameter {
|
impl Parse for Parameter {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let name = input.call(Ident::parse_any)?;
|
let name = Ident::parse_any(input)?;
|
||||||
let param = match name.to_string().as_str() {
|
match name.to_string().as_str() {
|
||||||
"methods" => {
|
"methods" => {
|
||||||
let content;
|
let content;
|
||||||
braced!(content in input);
|
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 methods = Vec::with_capacity(parsed.len());
|
||||||
let mut signatures = HashSet::new();
|
let mut signatures = HashSet::new();
|
||||||
let mut aliases = HashSet::new();
|
let mut aliases = HashSet::new();
|
||||||
for method in parsed {
|
for method in parsed {
|
||||||
if !signatures.insert(method.signature.clone()) {
|
if !signatures.insert(method.signature.clone()) {
|
||||||
return Err(Error::new(
|
return Err(Error::new(method.span(), "duplicate method signature"))
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
if !aliases.insert(method.alias.clone()) {
|
||||||
methods
|
return Err(Error::new(method.alias.span(), "duplicate method alias"))
|
||||||
};
|
}
|
||||||
|
methods.push(method.into_inner());
|
||||||
Parameter::Methods(methods)
|
}
|
||||||
|
Ok(Parameter::Methods(methods))
|
||||||
}
|
}
|
||||||
"derives" | "event_derives" => {
|
"derives" | "event_derives" => {
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
let derives = content
|
let derives = content.parse_terminated::<_, Token![,]>(Path::parse)?;
|
||||||
.parse_terminated::<_, Token![,]>(Path::parse)?
|
Ok(Parameter::Derives(derives))
|
||||||
.into_iter()
|
|
||||||
.map(|path| path.to_token_stream().to_string())
|
|
||||||
.collect();
|
|
||||||
Parameter::Derives(derives)
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => Err(Error::new(name.span(), "unexpected named parameter")),
|
||||||
return Err(Error::new(name.span(), format!("unexpected named parameter `{name}`")))
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(param)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,44 +153,40 @@ impl Parse for Parameter {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct Method {
|
struct Method {
|
||||||
signature: String,
|
signature: String,
|
||||||
alias: String,
|
alias: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Method {
|
impl Parse for Method {
|
||||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let function = {
|
// `{name}({params.join(",")})`
|
||||||
let name = input.parse::<Ident>()?.to_string();
|
let mut signature = String::with_capacity(64);
|
||||||
|
|
||||||
let content;
|
// function name
|
||||||
parenthesized!(content in input);
|
let name = input.parse::<Ident>()?;
|
||||||
let inputs = content
|
signature.push_str(&name.to_string());
|
||||||
.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<_>>>()?;
|
|
||||||
|
|
||||||
#[allow(deprecated)]
|
// function params
|
||||||
Function {
|
let content;
|
||||||
name,
|
parenthesized!(content in input);
|
||||||
inputs,
|
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
|
signature.push('(');
|
||||||
// affect its signature.
|
for (i, param) in params.into_iter().enumerate() {
|
||||||
outputs: vec![],
|
let s = param.to_string();
|
||||||
state_mutability: StateMutability::NonPayable,
|
// validate
|
||||||
constant: None,
|
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]>()?;
|
input.parse::<Token![as]>()?;
|
||||||
let alias = {
|
|
||||||
let ident = input.parse::<Ident>()?;
|
let alias = input.parse()?;
|
||||||
ident.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Method { signature, alias })
|
Ok(Method { signature, alias })
|
||||||
}
|
}
|
||||||
|
@ -238,43 +195,80 @@ impl Parse for Method {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::parse::Parser;
|
||||||
|
|
||||||
macro_rules! contract_args_result {
|
macro_rules! contract_args_result {
|
||||||
($($arg:tt)*) => {{
|
($($tt:tt)+) => {{
|
||||||
use syn::parse::Parser;
|
Parser::parse2(Contracts::parse, quote!($($tt)+))
|
||||||
<Spanned<ContractArgs> as Parse>::parse
|
|
||||||
.parse2(quote::quote! { $($arg)* })
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! contract_args {
|
macro_rules! contract_args {
|
||||||
($($arg:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
contract_args_result!($($arg)*)
|
contract_args_result!($($tt)*)
|
||||||
.expect("failed to parse contract args")
|
.expect("failed to parse contract args")
|
||||||
.into_inner()
|
.inner
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! contract_args_err {
|
macro_rules! contract_args_err {
|
||||||
($($arg:tt)*) => {
|
($($tt:tt)*) => {
|
||||||
contract_args_result!($($arg)*)
|
contract_args_result!($($tt)*)
|
||||||
.expect_err("expected parse contract args to error")
|
.expect_err("expected parse contract args to error")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
fn method(signature: &str, alias: &str) -> Method {
|
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> {
|
// Note: AST structs implement PartialEq by comparing the string repr, so the span is ignored.
|
||||||
use syn::parse::Parser;
|
fn arg(
|
||||||
Contracts::parse.parse2(s).unwrap().inner.into_iter().map(|(_, c)| c).collect::<Vec<_>>()
|
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]
|
#[test]
|
||||||
fn parse_multi_contract_args_events() {
|
fn parse_multi_contract_args_events() {
|
||||||
let args = parse_contracts(quote::quote! {
|
let args = contract_args! {
|
||||||
TestContract,
|
TestContract,
|
||||||
"path/to/abi.json",
|
"path/to/abi.json",
|
||||||
event_derives(serde::Deserialize, serde::Serialize);
|
event_derives(serde::Deserialize, serde::Serialize);
|
||||||
|
@ -282,96 +276,85 @@ mod tests {
|
||||||
TestContract2,
|
TestContract2,
|
||||||
"other.json",
|
"other.json",
|
||||||
event_derives(serde::Deserialize, serde::Serialize);
|
event_derives(serde::Deserialize, serde::Serialize);
|
||||||
});
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args,
|
args,
|
||||||
vec![
|
vec![
|
||||||
ContractArgs {
|
arg(
|
||||||
name: "TestContract".to_string(),
|
"TestContract",
|
||||||
abi: "path/to/abi.json".to_string(),
|
"path/to/abi.json",
|
||||||
parameters: vec![Parameter::Derives(vec![
|
[derives(["serde::Deserialize", "serde::Serialize"], false)],
|
||||||
"serde :: Deserialize".into(),
|
false
|
||||||
"serde :: Serialize".into(),
|
),
|
||||||
])],
|
arg(
|
||||||
},
|
"TestContract2",
|
||||||
ContractArgs {
|
"other.json",
|
||||||
name: "TestContract2".to_string(),
|
[derives(["serde::Deserialize", "serde::Serialize"], false)],
|
||||||
abi: "other.json".to_string(),
|
false
|
||||||
parameters: vec![Parameter::Derives(vec![
|
),
|
||||||
"serde :: Deserialize".into(),
|
|
||||||
"serde :: Serialize".into(),
|
|
||||||
])],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multi_contract_args_methods() {
|
fn parse_multi_contract_args_methods() {
|
||||||
let args = parse_contracts(quote::quote! {
|
let args = contract_args! {
|
||||||
TestContract,
|
TestContract,
|
||||||
"path/to/abi.json",
|
"path/to/abi.json",
|
||||||
methods {
|
methods {
|
||||||
myMethod(uint256, bool) as my_renamed_method;
|
myMethod(uint256, bool) as my_renamed_method;
|
||||||
myOtherMethod() as my_other_renamed_method;
|
myOtherMethod() as my_other_renamed_method;
|
||||||
}
|
};
|
||||||
;
|
|
||||||
|
|
||||||
TestContract2,
|
TestContract2,
|
||||||
"other.json",
|
"other.json",
|
||||||
event_derives(serde::Deserialize, serde::Serialize);
|
event_derives(serde::Deserialize, serde::Serialize);
|
||||||
});
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args,
|
args,
|
||||||
vec![
|
vec![
|
||||||
ContractArgs {
|
arg(
|
||||||
name: "TestContract".to_string(),
|
"TestContract",
|
||||||
abi: "path/to/abi.json".to_string(),
|
"path/to/abi.json",
|
||||||
parameters: vec![Parameter::Methods(vec![
|
[Parameter::Methods(vec![
|
||||||
method("myMethod(uint256,bool)", "my_renamed_method"),
|
method("myMethod(uint256,bool)", "my_renamed_method"),
|
||||||
method("myOtherMethod()", "my_other_renamed_method"),
|
method("myOtherMethod()", "my_other_renamed_method"),
|
||||||
])],
|
])],
|
||||||
},
|
false
|
||||||
ContractArgs {
|
),
|
||||||
name: "TestContract2".to_string(),
|
arg(
|
||||||
abi: "other.json".to_string(),
|
"TestContract2",
|
||||||
parameters: vec![Parameter::Derives(vec![
|
"other.json",
|
||||||
"serde :: Deserialize".into(),
|
[derives(["serde::Deserialize", "serde::Serialize"], false)],
|
||||||
"serde :: Serialize".into(),
|
false
|
||||||
])],
|
),
|
||||||
},
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multi_contract_args() {
|
fn parse_multi_contract_args() {
|
||||||
let args = parse_contracts(quote::quote! {
|
let args = contract_args! {
|
||||||
TestContract,
|
TestContract,
|
||||||
"path/to/abi.json",;
|
"path/to/abi.json",;
|
||||||
|
|
||||||
TestContract2,
|
TestContract2,
|
||||||
"other.json",
|
"other.json",
|
||||||
event_derives(serde::Deserialize, serde::Serialize);
|
event_derives(serde::Deserialize, serde::Serialize,);
|
||||||
});
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args,
|
args,
|
||||||
vec![
|
vec![
|
||||||
ContractArgs {
|
arg("TestContract", "path/to/abi.json", [], false),
|
||||||
name: "TestContract".to_string(),
|
arg(
|
||||||
abi: "path/to/abi.json".to_string(),
|
"TestContract2",
|
||||||
parameters: vec![],
|
"other.json",
|
||||||
},
|
[derives(["serde::Deserialize", "serde::Serialize"], true)],
|
||||||
ContractArgs {
|
false
|
||||||
name: "TestContract2".to_string(),
|
),
|
||||||
abi: "other.json".to_string(),
|
|
||||||
parameters: vec![Parameter::Derives(vec![
|
|
||||||
"serde :: Deserialize".into(),
|
|
||||||
"serde :: Serialize".into(),
|
|
||||||
])],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -379,21 +362,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_contract_args() {
|
fn parse_contract_args() {
|
||||||
let args = contract_args!(TestContract, "path/to/abi.json");
|
let args = contract_args!(TestContract, "path/to/abi.json");
|
||||||
assert_eq!(args.name, "TestContract");
|
assert_eq!(*args.first().unwrap(), arg("TestContract", "path/to/abi.json", [], false));
|
||||||
assert_eq!(args.abi, "path/to/abi.json");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_contract_args_with_defaults() {
|
fn parse_contract_args_with_defaults() {
|
||||||
let args = contract_args!(TestContract, "[{}]");
|
let args = contract_args!(TestContract, "[{}]");
|
||||||
assert_eq!(
|
assert_eq!(*args.first().unwrap(), arg("TestContract", "[{}]", [], false));
|
||||||
args,
|
|
||||||
ContractArgs {
|
|
||||||
name: "TestContract".to_string(),
|
|
||||||
abi: "[{}]".to_string(),
|
|
||||||
parameters: vec![],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -408,23 +383,19 @@ mod tests {
|
||||||
event_derives (Asdf, a::B, a::b::c::D)
|
event_derives (Asdf, a::B, a::b::c::D)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
args,
|
*args.first().unwrap(),
|
||||||
ContractArgs {
|
arg(
|
||||||
name: "TestContract".to_string(),
|
"TestContract",
|
||||||
abi: "abi.json".to_string(),
|
"abi.json",
|
||||||
parameters: vec![
|
[
|
||||||
// Parameter::Contract("Contract".into()),
|
|
||||||
Parameter::Methods(vec![
|
Parameter::Methods(vec![
|
||||||
method("myMethod(uint256,bool)", "my_renamed_method"),
|
method("myMethod(uint256,bool)", "my_renamed_method"),
|
||||||
method("myOtherMethod()", "my_other_renamed_method"),
|
method("myOtherMethod()", "my_other_renamed_method"),
|
||||||
]),
|
]),
|
||||||
Parameter::Derives(vec![
|
derives(["Asdf", "a::B", "a::b::c::D"], false)
|
||||||
"Asdf".into(),
|
|
||||||
"a :: B".into(),
|
|
||||||
"a :: b :: c :: D".into()
|
|
||||||
])
|
|
||||||
],
|
],
|
||||||
},
|
false
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
//! Implementation of procedural macro for generating type-safe bindings to an
|
//! Procedural macros for generating type-safe bindings to an Ethereum smart contract.
|
||||||
//! ethereum smart contract.
|
|
||||||
#![deny(missing_docs, unsafe_code, unused_crate_dependencies)]
|
#![deny(missing_docs, unsafe_code, unused_crate_dependencies)]
|
||||||
#![deny(rustdoc::broken_intra_doc_links)]
|
#![deny(rustdoc::broken_intra_doc_links)]
|
||||||
|
|
||||||
|
use abigen::Contracts;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
use abigen::Contracts;
|
|
||||||
|
|
||||||
pub(crate) mod abi_ty;
|
pub(crate) mod abi_ty;
|
||||||
mod abigen;
|
mod abigen;
|
||||||
mod call;
|
mod call;
|
||||||
|
@ -19,18 +18,42 @@ mod event;
|
||||||
mod spanned;
|
mod spanned;
|
||||||
pub(crate) mod utils;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
/// Proc macro to generate type-safe bindings to a contract(s). This macro
|
/// Generates type-safe bindings to an Ethereum smart contract from its ABI.
|
||||||
/// accepts one or more Ethereum contract ABI or a path. Note that relative paths are
|
///
|
||||||
/// rooted in the crate's root `CARGO_MANIFEST_DIR`.
|
/// All the accepted ABI sources are listed in the examples below and in [Source].
|
||||||
/// Environment variable interpolation is supported via `$` prefix, like
|
///
|
||||||
/// `"$CARGO_MANIFEST_DIR/contracts/c.json"`
|
/// 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
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
/// All the possible ABI sources:
|
||||||
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// # use ethers_contract_derive::abigen;
|
/// use ethers_contract_derive::abigen;
|
||||||
|
///
|
||||||
/// // ABI Path
|
/// // ABI Path
|
||||||
/// abigen!(MyContract, "MyContract.json");
|
/// abigen!(MyContract, "./MyContractABI.json");
|
||||||
///
|
///
|
||||||
/// // HTTP(S) source
|
/// // HTTP(S) source
|
||||||
/// abigen!(MyContract, "https://my.domain.local/path/to/contract.json");
|
/// 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
|
/// Specify additional parameters:
|
||||||
/// `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
|
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// abigen!(
|
/// abigen!(
|
||||||
|
@ -71,7 +82,7 @@ pub(crate) mod utils;
|
||||||
/// methods {
|
/// methods {
|
||||||
/// myMethod(uint256,bool) as my_renamed_method;
|
/// 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
|
/// `abigen!` bundles all type duplicates so that all rust contracts also use
|
||||||
/// the same rust types.
|
/// the same rust types.
|
||||||
///
|
///
|
||||||
/// # Example Multiple contracts
|
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// abigen!(
|
/// abigen!(
|
||||||
/// MyContract,
|
/// MyContract,
|
||||||
|
@ -91,18 +101,21 @@ pub(crate) mod utils;
|
||||||
/// methods {
|
/// methods {
|
||||||
/// myMethod(uint256,bool) as my_renamed_method;
|
/// myMethod(uint256,bool) as my_renamed_method;
|
||||||
/// },
|
/// },
|
||||||
/// event_derives (serde::Deserialize, serde::Serialize);
|
/// derives(serde::Deserialize, serde::Serialize);
|
||||||
///
|
///
|
||||||
/// MyOtherContract,
|
/// MyOtherContract,
|
||||||
/// "path/to/MyOtherContract.json",
|
/// "path/to/MyOtherContract.json",
|
||||||
/// event_derives (serde::Deserialize, serde::Serialize);
|
/// derives(serde::Deserialize, serde::Serialize);
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn abigen(input: TokenStream) -> TokenStream {
|
pub fn abigen(input: TokenStream) -> TokenStream {
|
||||||
let contracts = parse_macro_input!(input as Contracts);
|
let contracts = parse_macro_input!(input as Contracts);
|
||||||
|
match contracts.expand() {
|
||||||
contracts.expand().unwrap_or_else(|err| err.to_compile_error()).into()
|
Ok(tokens) => tokens,
|
||||||
|
Err(err) => err.to_compile_error(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derives the `AbiType` and all `Tokenizable` traits for the labeled type.
|
/// 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)]
|
#[proc_macro_derive(EthAbiCodec)]
|
||||||
pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
|
pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
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
|
/// Derives `fmt::Display` trait and generates a convenient format for all the
|
||||||
|
|
Loading…
Reference in New Issue