From ea8551da4c94ee68c4ade3daa3f32e81b6706657 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 11 Oct 2021 16:18:09 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + .../ethers-contract-abigen/src/contract.rs | 91 +++++-- .../src/contract/structs.rs | 193 +++++++++------ .../ethers-contract-abigen/src/lib.rs | 5 +- .../ethers-contract-derive/src/abigen.rs | 228 +++++++++++++++++- .../ethers-contract-derive/src/lib.rs | 36 ++- ethers-contract/tests/abigen.rs | 79 ++++++ 7 files changed, 515 insertions(+), 118 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f552fc1c..bd991759 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs index 34bc0266..b226d2f6 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract.rs @@ -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 { - let cx = Self::from_abigen(args)?; - let name = &cx.contract_name; + /// Expands the whole rust contract + pub fn expand(&self) -> Result { + 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 { @@ -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 { + pub fn from_abigen(args: Abigen) -> Result { // 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 + } } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs index 7e6273ac..c48974ce 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs @@ -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 { + 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 { + 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 { 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 { + 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::>() + .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 { 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::>() - .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 { + &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`. diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs index 58643ccf..6ef9ac24 100644 --- a/ethers-contract/ethers-contract-abigen/src/lib.rs +++ b/ethers-contract/ethers-contract-abigen/src/lib.rs @@ -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 { let rustfmt = self.rustfmt; - let tokens = Context::expand(self)?; + let tokens = Context::from_abigen(self)?.expand()?.into_tokens(); Ok(ContractBindings { tokens, rustfmt }) } } diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs index f0cfc2e3..ef47fb2c 100644 --- a/ethers-contract/ethers-contract-derive/src/abigen.rs +++ b/ethers-contract/ethers-contract-derive/src/abigen.rs @@ -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> { - 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 { + 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> = 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> { + let contract = contract.into_builder()?; + let ctx = Context::from_abigen(contract)?; + Ok((ctx.expand()?, ctx)) + } +} + +impl Parse for Contracts { + fn parse(input: ParseStream) -> ParseResult { + 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::()?; - } - 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::()?; + } + } + } Ok(( span, @@ -232,6 +325,121 @@ mod tests { } } + fn parse_contracts(s: TokenStream2) -> Vec { + use syn::parse::Parser; + Contracts::parse + .parse2(s) + .unwrap() + .inner + .into_iter() + .map(|(_, c)| c) + .collect::>() + } + + #[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"); diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs index cf8f5646..c0ba36d2 100644 --- a/ethers-contract/ethers-contract-derive/src/lib.rs +++ b/ethers-contract/ethers-contract-derive/src/lib.rs @@ -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); + 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() } diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 1febb0ea..c41f0f09 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -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::(); } +#[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::(); + assert_tokenizeable::(); + assert_tokenizeable::(); + + 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!(