Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for multiple contract definitions in abigen macro #498

Merged
merged 5 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
91 changes: 69 additions & 22 deletions ethers-contract/ethers-contract-abigen/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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()));
Expand All @@ -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> {
Expand All @@ -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();
Expand Down Expand Up @@ -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
}
}
179 changes: 110 additions & 69 deletions ethers-contract/ethers-contract-abigen/src/contract/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,53 @@ 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();
let mut ids: Vec<_> = self.internal_structs.structs.keys().collect();
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)
}
Expand Down Expand Up @@ -113,75 +140,83 @@ impl Context {
})
}

/// 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
));
}
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 = format!(
"{}({})",
name,
param_types
.iter()
.map(|kind| kind.to_string())
.collect::<Vec<_>>()
.join(","),
);

let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));

let name = util::ident(name);
let name = util::ident(name);

// use the same derives as for events
let derives = &self.event_derives;
let derives = quote! {#(#derives),*};
// use the same derives as for events
let derives = &self.event_derives;
let derives = quote! {#(#derives),*};

let ethers_contract = util::ethers_contract_crate();
let ethers_contract = util::ethers_contract_crate();

structs.extend(quote! {
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 {
structs.extend(self.generate_human_readable_struct(name)?);
}
Ok(structs)
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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`.
Expand Down
5 changes: 3 additions & 2 deletions ethers-contract/ethers-contract-abigen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 })
}
}
Expand Down
Loading