feat: add support for multiple contract definitions in abigen macro (#498)
* feat: support multiple contracts in abigen * fix: use correct events decl * fix: parsing and tests * test: add test * chore: update changelog
This commit is contained in:
parent
6d9b300b86
commit
ea8551da4c
|
@ -4,6 +4,7 @@
|
|||
|
||||
### Unreleased
|
||||
|
||||
- `abigen!` now supports multiple contracts [#498](https://github.com/gakonst/ethers-rs/pull/498)
|
||||
- Use rust types as contract function inputs for human readable abi [#482](https://github.com/gakonst/ethers-rs/pull/482)
|
||||
- Add EIP-712 `sign_typed_data` signer method; add ethers-core type `Eip712` trait and derive macro in ethers-derive-eip712 [#481](https://github.com/gakonst/ethers-rs/pull/481)
|
||||
|
||||
|
|
|
@ -18,8 +18,48 @@ use serde::Deserialize;
|
|||
use std::collections::BTreeMap;
|
||||
use syn::Path;
|
||||
|
||||
/// The result of `Context::expand`
|
||||
#[derive(Debug)]
|
||||
pub struct ExpandedContract {
|
||||
/// The name of the contract module
|
||||
pub module: Ident,
|
||||
/// The contract module's imports
|
||||
pub imports: TokenStream,
|
||||
/// Contract, Middle related implementations
|
||||
pub contract: TokenStream,
|
||||
/// All event impls of the contract
|
||||
pub events: TokenStream,
|
||||
/// The contract's internal structs
|
||||
pub abi_structs: TokenStream,
|
||||
}
|
||||
|
||||
impl ExpandedContract {
|
||||
/// Merges everything into a single module
|
||||
pub fn into_tokens(self) -> TokenStream {
|
||||
let ExpandedContract {
|
||||
module,
|
||||
imports,
|
||||
contract,
|
||||
events,
|
||||
abi_structs,
|
||||
} = self;
|
||||
quote! {
|
||||
// export all the created data types
|
||||
pub use #module::*;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
mod #module {
|
||||
#imports
|
||||
#contract
|
||||
#events
|
||||
#abi_structs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal shared context for generating smart contract bindings.
|
||||
pub(crate) struct Context {
|
||||
pub struct Context {
|
||||
/// The ABI string pre-parsing.
|
||||
abi_str: Literal,
|
||||
|
||||
|
@ -49,12 +89,12 @@ pub(crate) struct Context {
|
|||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn expand(args: Abigen) -> Result<TokenStream> {
|
||||
let cx = Self::from_abigen(args)?;
|
||||
let name = &cx.contract_name;
|
||||
/// Expands the whole rust contract
|
||||
pub fn expand(&self) -> Result<ExpandedContract> {
|
||||
let name = &self.contract_name;
|
||||
let name_mod = util::ident(&format!(
|
||||
"{}_mod",
|
||||
cx.contract_name.to_string().to_lowercase()
|
||||
self.contract_name.to_string().to_lowercase()
|
||||
));
|
||||
|
||||
let abi_name = super::util::safe_ident(&format!("{}_ABI", name.to_string().to_uppercase()));
|
||||
|
@ -63,31 +103,25 @@ impl Context {
|
|||
let imports = common::imports(&name.to_string());
|
||||
|
||||
// 1. Declare Contract struct
|
||||
let struct_decl = common::struct_declaration(&cx, &abi_name);
|
||||
let struct_decl = common::struct_declaration(self, &abi_name);
|
||||
|
||||
// 2. Declare events structs & impl FromTokens for each event
|
||||
let events_decl = cx.events_declaration()?;
|
||||
let events_decl = self.events_declaration()?;
|
||||
|
||||
// 3. impl block for the event functions
|
||||
let contract_events = cx.event_methods()?;
|
||||
let contract_events = self.event_methods()?;
|
||||
|
||||
// 4. impl block for the contract methods
|
||||
let contract_methods = cx.methods()?;
|
||||
let contract_methods = self.methods()?;
|
||||
|
||||
// 5. Declare the structs parsed from the human readable abi
|
||||
let abi_structs_decl = cx.abi_structs()?;
|
||||
let abi_structs_decl = self.abi_structs()?;
|
||||
|
||||
let ethers_core = util::ethers_core_crate();
|
||||
let ethers_contract = util::ethers_contract_crate();
|
||||
let ethers_providers = util::ethers_providers_crate();
|
||||
|
||||
Ok(quote! {
|
||||
// export all the created data types
|
||||
pub use #name_mod::*;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
mod #name_mod {
|
||||
#imports
|
||||
let contract = quote! {
|
||||
#struct_decl
|
||||
|
||||
impl<'a, M: #ethers_providers::Middleware> #name<M> {
|
||||
|
@ -105,16 +139,19 @@ impl Context {
|
|||
|
||||
#contract_events
|
||||
}
|
||||
};
|
||||
|
||||
#events_decl
|
||||
|
||||
#abi_structs_decl
|
||||
}
|
||||
Ok(ExpandedContract {
|
||||
module: name_mod,
|
||||
imports,
|
||||
contract,
|
||||
events: events_decl,
|
||||
abi_structs: abi_structs_decl,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a context from the code generation arguments.
|
||||
fn from_abigen(args: Abigen) -> Result<Self> {
|
||||
pub fn from_abigen(args: Abigen) -> Result<Self> {
|
||||
// get the actual ABI string
|
||||
let abi_str = args.abi_source.get().context("failed to get ABI JSON")?;
|
||||
let mut abi_parser = AbiParser::default();
|
||||
|
@ -202,4 +239,14 @@ impl Context {
|
|||
event_aliases,
|
||||
})
|
||||
}
|
||||
|
||||
/// The internal abi struct mapping table
|
||||
pub fn internal_structs(&self) -> &InternalStructs {
|
||||
&self.internal_structs
|
||||
}
|
||||
|
||||
/// The internal mutable abi struct mapping table
|
||||
pub fn internal_structs_mut(&mut self) -> &mut InternalStructs {
|
||||
&mut self.internal_structs
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,45 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
/// In the event of type conflicts this allows for removing a specific struct type.
|
||||
pub fn remove_struct(&mut self, name: &str) {
|
||||
if self.human_readable {
|
||||
self.abi_parser.structs.remove(name);
|
||||
} else {
|
||||
self.internal_structs.structs.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type definition for the struct with the given name
|
||||
pub fn struct_definition(&mut self, name: &str) -> Result<TokenStream> {
|
||||
if self.human_readable {
|
||||
self.generate_human_readable_struct(name)
|
||||
} else {
|
||||
self.generate_internal_struct(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the type definition for the name that matches the given identifier
|
||||
fn generate_internal_struct(&self, id: &str) -> Result<TokenStream> {
|
||||
let sol_struct = self
|
||||
.internal_structs
|
||||
.structs
|
||||
.get(id)
|
||||
.context("struct not found")?;
|
||||
let struct_name = self
|
||||
.internal_structs
|
||||
.rust_type_names
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?;
|
||||
let tuple = self
|
||||
.internal_structs
|
||||
.struct_tuples
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?
|
||||
.clone();
|
||||
self.expand_internal_struct(struct_name, sol_struct, tuple)
|
||||
}
|
||||
|
||||
/// Returns the `TokenStream` with all the internal structs extracted form the JSON ABI
|
||||
fn gen_internal_structs(&self) -> Result<TokenStream> {
|
||||
let mut structs = TokenStream::new();
|
||||
|
@ -37,19 +76,7 @@ impl Context {
|
|||
ids.sort();
|
||||
|
||||
for id in ids {
|
||||
let sol_struct = &self.internal_structs.structs[id];
|
||||
let struct_name = self
|
||||
.internal_structs
|
||||
.rust_type_names
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?;
|
||||
let tuple = self
|
||||
.internal_structs
|
||||
.struct_tuples
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?
|
||||
.clone();
|
||||
structs.extend(self.expand_internal_struct(struct_name, sol_struct, tuple)?);
|
||||
structs.extend(self.generate_internal_struct(id)?);
|
||||
}
|
||||
Ok(structs)
|
||||
}
|
||||
|
@ -113,75 +140,83 @@ impl Context {
|
|||
})
|
||||
}
|
||||
|
||||
fn generate_human_readable_struct(&self, name: &str) -> Result<TokenStream> {
|
||||
let sol_struct = self
|
||||
.abi_parser
|
||||
.structs
|
||||
.get(name)
|
||||
.context("struct not found")?;
|
||||
let mut fields = Vec::with_capacity(sol_struct.fields().len());
|
||||
let mut param_types = Vec::with_capacity(sol_struct.fields().len());
|
||||
for field in sol_struct.fields() {
|
||||
let field_name = util::ident(&field.name().to_snake_case());
|
||||
match field.r#type() {
|
||||
FieldType::Elementary(ty) => {
|
||||
param_types.push(ty.clone());
|
||||
let ty = types::expand(ty)?;
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
}
|
||||
FieldType::Struct(struct_ty) => {
|
||||
let ty = expand_struct_type(struct_ty);
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
|
||||
let name = struct_ty.name();
|
||||
let tuple = self
|
||||
.abi_parser
|
||||
.struct_tuples
|
||||
.get(name)
|
||||
.context(format!("No types found for {}", name))?
|
||||
.clone();
|
||||
let tuple = ParamType::Tuple(tuple);
|
||||
|
||||
param_types.push(struct_ty.as_param(tuple));
|
||||
}
|
||||
FieldType::Mapping(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mapping types in struct `{}` are not supported {:?}",
|
||||
name,
|
||||
field
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let abi_signature = format!(
|
||||
"{}({})",
|
||||
name,
|
||||
param_types
|
||||
.iter()
|
||||
.map(|kind| kind.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
|
||||
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
|
||||
|
||||
let name = util::ident(name);
|
||||
|
||||
// use the same derives as for events
|
||||
let derives = &self.event_derives;
|
||||
let derives = quote! {#(#derives),*};
|
||||
|
||||
let ethers_contract = util::ethers_contract_crate();
|
||||
|
||||
Ok(quote! {
|
||||
#abi_signature_doc
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
|
||||
pub struct #name {
|
||||
#( #fields ),*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Expand all structs parsed from the human readable ABI
|
||||
fn gen_human_readable_structs(&self) -> Result<TokenStream> {
|
||||
let mut structs = TokenStream::new();
|
||||
let mut names: Vec<_> = self.abi_parser.structs.keys().collect();
|
||||
names.sort();
|
||||
for name in names {
|
||||
let sol_struct = &self.abi_parser.structs[name];
|
||||
let mut fields = Vec::with_capacity(sol_struct.fields().len());
|
||||
let mut param_types = Vec::with_capacity(sol_struct.fields().len());
|
||||
for field in sol_struct.fields() {
|
||||
let field_name = util::ident(&field.name().to_snake_case());
|
||||
match field.r#type() {
|
||||
FieldType::Elementary(ty) => {
|
||||
param_types.push(ty.clone());
|
||||
let ty = types::expand(ty)?;
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
}
|
||||
FieldType::Struct(struct_ty) => {
|
||||
let ty = expand_struct_type(struct_ty);
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
|
||||
let name = struct_ty.name();
|
||||
let tuple = self
|
||||
.abi_parser
|
||||
.struct_tuples
|
||||
.get(name)
|
||||
.context(format!("No types found for {}", name))?
|
||||
.clone();
|
||||
let tuple = ParamType::Tuple(tuple);
|
||||
|
||||
param_types.push(struct_ty.as_param(tuple));
|
||||
}
|
||||
FieldType::Mapping(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mapping types in struct `{}` are not supported {:?}",
|
||||
name,
|
||||
field
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let abi_signature = format!(
|
||||
"{}({})",
|
||||
name,
|
||||
param_types
|
||||
.iter()
|
||||
.map(|kind| kind.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
|
||||
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
|
||||
|
||||
let name = util::ident(name);
|
||||
|
||||
// use the same derives as for events
|
||||
let derives = &self.event_derives;
|
||||
let derives = quote! {#(#derives),*};
|
||||
|
||||
let ethers_contract = util::ethers_contract_crate();
|
||||
|
||||
structs.extend(quote! {
|
||||
#abi_signature_doc
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
|
||||
pub struct #name {
|
||||
#( #fields ),*
|
||||
}
|
||||
});
|
||||
structs.extend(self.generate_human_readable_struct(name)?);
|
||||
}
|
||||
Ok(structs)
|
||||
}
|
||||
|
@ -209,6 +244,7 @@ pub struct InternalStructs {
|
|||
}
|
||||
|
||||
impl InternalStructs {
|
||||
/// Creates a new instance with a filled type mapping table based on the abi
|
||||
pub fn new(abi: RawAbi) -> Self {
|
||||
let mut top_level_internal_types = HashMap::new();
|
||||
let mut function_params = HashMap::new();
|
||||
|
@ -285,6 +321,11 @@ impl InternalStructs {
|
|||
.and_then(|id| self.rust_type_names.get(id))
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
/// Returns the mapping table of abi `internal type identifier -> rust type`
|
||||
pub fn rust_type_names(&self) -> &HashMap<String, String> {
|
||||
&self.rust_type_names
|
||||
}
|
||||
}
|
||||
|
||||
/// This will determine the name of the rust type and will make sure that possible collisions are resolved by adjusting the actual Rust name of the structure, e.g. `LibraryA.Point` and `LibraryB.Point` to `LibraryAPoint` and `LibraryBPoint`.
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
#[path = "test/macros.rs"]
|
||||
mod test_macros;
|
||||
|
||||
mod contract;
|
||||
/// Contains types to generate rust bindings for solidity contracts
|
||||
pub mod contract;
|
||||
use contract::Context;
|
||||
|
||||
pub mod rawabi;
|
||||
|
@ -126,7 +127,7 @@ impl Abigen {
|
|||
/// Generates the contract bindings.
|
||||
pub fn generate(self) -> Result<ContractBindings> {
|
||||
let rustfmt = self.rustfmt;
|
||||
let tokens = Context::expand(self)?;
|
||||
let tokens = Context::from_abigen(self)?.expand()?.into_tokens();
|
||||
Ok(ContractBindings { tokens, rustfmt })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,96 @@ use crate::spanned::{ParseInner, Spanned};
|
|||
use ethers_contract_abigen::Abigen;
|
||||
use ethers_core::abi::{Function, FunctionExt, Param, StateMutability};
|
||||
|
||||
use ethers_contract_abigen::contract::{Context, ExpandedContract};
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::ToTokens;
|
||||
use std::collections::HashSet;
|
||||
use quote::{quote, ToTokens};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use syn::ext::IdentExt;
|
||||
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
|
||||
use syn::{braced, parenthesized, Ident, LitStr, Path, Token};
|
||||
|
||||
pub(crate) fn expand(args: ContractArgs) -> Result<TokenStream2, Box<dyn Error>> {
|
||||
Ok(args.into_builder()?.generate()?.into_tokens())
|
||||
/// A series of `ContractArgs` separated by `;`
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub(crate) struct Contracts {
|
||||
inner: Vec<(Span, ContractArgs)>,
|
||||
}
|
||||
|
||||
impl Contracts {
|
||||
pub(crate) fn expand(self) -> Result<TokenStream2, syn::Error> {
|
||||
let mut tokens = TokenStream2::new();
|
||||
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| syn::Error::new(span, err.to_string()))?;
|
||||
expansions.push(contract);
|
||||
}
|
||||
|
||||
// merge all types if more than 1 contract
|
||||
if expansions.len() > 1 {
|
||||
// check for type conflicts
|
||||
let mut conflicts: HashMap<String, Vec<usize>> = HashMap::new();
|
||||
for (idx, (_, ctx)) in expansions.iter().enumerate() {
|
||||
for type_identifier in ctx.internal_structs().rust_type_names().keys() {
|
||||
conflicts
|
||||
.entry(type_identifier.clone())
|
||||
.or_insert_with(|| Vec::with_capacity(1))
|
||||
.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
let mut shared_types = TokenStream2::new();
|
||||
let shared_types_mdoule = quote!(__shared_types);
|
||||
let mut dirty = HashSet::new();
|
||||
// resolve type conflicts
|
||||
for (id, contracts) in conflicts.iter().filter(|(_, c)| c.len() > 1) {
|
||||
// extract the shared type once
|
||||
shared_types.extend(expansions[contracts[0]].1.struct_definition(id).unwrap());
|
||||
// remove the shared type
|
||||
for contract in contracts.iter().copied() {
|
||||
expansions[contract].1.remove_struct(id);
|
||||
dirty.insert(contract);
|
||||
}
|
||||
}
|
||||
|
||||
// regenerate all struct definitions that were hit and adjust imports
|
||||
for contract in dirty {
|
||||
let (expanded, ctx) = &mut expansions[contract];
|
||||
expanded.abi_structs = ctx.abi_structs().unwrap();
|
||||
expanded
|
||||
.imports
|
||||
.extend(quote!( pub use super::#shared_types_mdoule::*;));
|
||||
}
|
||||
tokens.extend(quote! {
|
||||
pub mod #shared_types_mdoule {
|
||||
#shared_types
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tokens.extend(expansions.into_iter().map(|(exp, _)| exp.into_tokens()));
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn expand_contract(
|
||||
contract: ContractArgs,
|
||||
) -> Result<(ExpandedContract, Context), Box<dyn Error>> {
|
||||
let contract = contract.into_builder()?;
|
||||
let ctx = Context::from_abigen(contract)?;
|
||||
Ok((ctx.expand()?, ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Contracts {
|
||||
fn parse(input: ParseStream) -> ParseResult<Self> {
|
||||
let inner = input
|
||||
.parse_terminated::<_, Token![;]>(ContractArgs::spanned_parse)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
Ok(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
/// Contract procedural macro arguments.
|
||||
|
@ -62,14 +142,27 @@ impl ParseInner for ContractArgs {
|
|||
(literal.span(), literal.value())
|
||||
};
|
||||
|
||||
if !input.is_empty() {
|
||||
let mut parameters = Vec::new();
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(Token![,]) {
|
||||
input.parse::<Token![,]>()?;
|
||||
}
|
||||
|
||||
let parameters = input
|
||||
.parse_terminated::<_, Token![,]>(Parameter::parse)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
loop {
|
||||
if input.is_empty() {
|
||||
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![,]>()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
span,
|
||||
|
@ -232,6 +325,121 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_contracts(s: TokenStream2) -> Vec<ContractArgs> {
|
||||
use syn::parse::Parser;
|
||||
Contracts::parse
|
||||
.parse2(s)
|
||||
.unwrap()
|
||||
.inner
|
||||
.into_iter()
|
||||
.map(|(_, c)| c)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multi_contract_args_events() {
|
||||
let args = parse_contracts(quote::quote! {
|
||||
TestContract,
|
||||
"path/to/abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize);
|
||||
|
||||
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::EventDerives(vec![
|
||||
"serde :: Deserialize".into(),
|
||||
"serde :: Serialize".into(),
|
||||
])],
|
||||
},
|
||||
ContractArgs {
|
||||
name: "TestContract2".to_string(),
|
||||
abi: "other.json".to_string(),
|
||||
parameters: vec![Parameter::EventDerives(vec![
|
||||
"serde :: Deserialize".into(),
|
||||
"serde :: Serialize".into(),
|
||||
])],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn parse_multi_contract_args_methods() {
|
||||
let args = parse_contracts(quote::quote! {
|
||||
TestContract,
|
||||
"path/to/abi.json",
|
||||
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![
|
||||
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::EventDerives(vec![
|
||||
"serde :: Deserialize".into(),
|
||||
"serde :: Serialize".into(),
|
||||
])],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multi_contract_args() {
|
||||
let args = parse_contracts(quote::quote! {
|
||||
TestContract,
|
||||
"path/to/abi.json",;
|
||||
|
||||
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![],
|
||||
},
|
||||
ContractArgs {
|
||||
name: "TestContract2".to_string(),
|
||||
abi: "other.json".to_string(),
|
||||
parameters: vec![Parameter::EventDerives(vec![
|
||||
"serde :: Deserialize".into(),
|
||||
"serde :: Serialize".into(),
|
||||
])],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_contract_args() {
|
||||
let args = contract_args!(TestContract, "path/to/abi.json");
|
||||
|
|
|
@ -12,16 +12,15 @@ use syn::{
|
|||
GenericArgument, Lit, Meta, NestedMeta, PathArguments, Type,
|
||||
};
|
||||
|
||||
use abigen::{expand, ContractArgs};
|
||||
use abigen::Contracts;
|
||||
use ethers_core::abi::{param_type::Reader, AbiParser, Event, EventExt, EventParam, ParamType};
|
||||
use hex::FromHex;
|
||||
use spanned::Spanned;
|
||||
|
||||
mod abigen;
|
||||
mod spanned;
|
||||
|
||||
/// Proc macro to generate type-safe bindings to a contract. This macro accepts
|
||||
/// an Ethereum contract ABI or a path. Note that this path is rooted in
|
||||
/// 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 this path is rooted in
|
||||
/// the crate's root `CARGO_MANIFEST_DIR`.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -55,6 +54,8 @@ mod spanned;
|
|||
/// - `event_derives`: A list of additional derives that should be added to
|
||||
/// contract event structs and enums.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// abigen!(
|
||||
/// MyContract,
|
||||
|
@ -65,13 +66,32 @@ mod spanned;
|
|||
/// event_derives (serde::Deserialize, serde::Serialize),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// `abigen!` supports multiple abigen definitions separated by a semicolon `;`
|
||||
/// This is useful if the contracts use ABIEncoderV2 structs. In which case `abigen!` bundles all type duplicates so that all rust contracts also use the same rust types.
|
||||
///
|
||||
/// # Example Multiple contracts
|
||||
/// ```ignore
|
||||
/// abigen!(
|
||||
/// MyContract,
|
||||
/// "path/to/MyContract.json",
|
||||
/// methods {
|
||||
/// myMethod(uint256,bool) as my_renamed_method;
|
||||
/// },
|
||||
/// event_derives (serde::Deserialize, serde::Serialize);
|
||||
///
|
||||
/// MyOtherContract,
|
||||
/// "path/to/MyOtherContract.json",
|
||||
/// event_derives (serde::Deserialize, serde::Serialize);
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn abigen(input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(input as Spanned<ContractArgs>);
|
||||
let contracts = parse_macro_input!(input as Contracts);
|
||||
|
||||
let span = args.span();
|
||||
expand(args.into_inner())
|
||||
.unwrap_or_else(|e| Error::new(span, format!("{:?}", e)).to_compile_error())
|
||||
contracts
|
||||
.expand()
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//! Test cases to validate the `abigen!` macro
|
||||
use ethers_contract::{abigen, EthEvent};
|
||||
use ethers_core::abi::{Address, Tokenizable};
|
||||
use ethers_core::types::U256;
|
||||
use ethers_providers::Provider;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -21,6 +22,33 @@ fn can_gen_human_readable() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_gen_human_readable_multiple() {
|
||||
abigen!(
|
||||
SimpleContract1,
|
||||
r#"[
|
||||
event ValueChanged1(address indexed author, string oldValue, string newValue)
|
||||
]"#,
|
||||
event_derives(serde::Deserialize, serde::Serialize);
|
||||
|
||||
SimpleContract2,
|
||||
r#"[
|
||||
event ValueChanged2(address indexed author, string oldValue, string newValue)
|
||||
]"#,
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
assert_eq!("ValueChanged1", ValueChanged1Filter::name());
|
||||
assert_eq!(
|
||||
"ValueChanged1(address,string,string)",
|
||||
ValueChanged1Filter::abi_signature()
|
||||
);
|
||||
assert_eq!("ValueChanged2", ValueChanged2Filter::name());
|
||||
assert_eq!(
|
||||
"ValueChanged2(address,string,string)",
|
||||
ValueChanged2Filter::abi_signature()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_gen_structs_readable() {
|
||||
abigen!(
|
||||
|
@ -77,6 +105,57 @@ fn can_generate_internal_structs() {
|
|||
assert_tokenizeable::<G2Point>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_generate_internal_structs_multiple() {
|
||||
// NOTE: nesting here is necessary due to how tests are structured...
|
||||
use contract::*;
|
||||
mod contract {
|
||||
use super::*;
|
||||
abigen!(
|
||||
VerifierContract,
|
||||
"ethers-contract/tests/solidity-contracts/verifier_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize);
|
||||
|
||||
MyOtherVerifierContract,
|
||||
"ethers-contract/tests/solidity-contracts/verifier_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize);
|
||||
);
|
||||
}
|
||||
assert_tokenizeable::<VerifyingKey>();
|
||||
assert_tokenizeable::<G1Point>();
|
||||
assert_tokenizeable::<G2Point>();
|
||||
|
||||
let (provider, _) = Provider::mocked();
|
||||
let client = Arc::new(provider);
|
||||
|
||||
let g1 = G1Point {
|
||||
x: U256::zero(),
|
||||
y: U256::zero(),
|
||||
};
|
||||
let g2 = G2Point {
|
||||
x: [U256::zero(), U256::zero()],
|
||||
y: [U256::zero(), U256::zero()],
|
||||
};
|
||||
let vk = VerifyingKey {
|
||||
alfa_1: g1.clone(),
|
||||
beta_2: g2.clone(),
|
||||
gamma_2: g2.clone(),
|
||||
delta_2: g2.clone(),
|
||||
ic: vec![g1.clone()],
|
||||
};
|
||||
let proof = Proof {
|
||||
a: g1.clone(),
|
||||
b: g2,
|
||||
c: g1,
|
||||
};
|
||||
|
||||
// ensure both contracts use the same types
|
||||
let contract = VerifierContract::new(Address::zero(), client.clone());
|
||||
let _ = contract.verify(vec![], proof.clone(), vk.clone());
|
||||
let contract = MyOtherVerifierContract::new(Address::zero(), client);
|
||||
let _ = contract.verify(vec![], proof, vk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_gen_human_readable_with_structs() {
|
||||
abigen!(
|
||||
|
|
Loading…
Reference in New Issue