Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
feat: add support for multiple contract definitions in abigen macro (#…
Browse files Browse the repository at this point in the history
…498)

* feat: support multiple contracts in abigen

* fix: use correct events decl

* fix: parsing and tests

* test: add test

* chore: update changelog
  • Loading branch information
mattsse authored Oct 11, 2021
1 parent 6d9b300 commit ea8551d
Show file tree
Hide file tree
Showing 7 changed files with 508 additions and 111 deletions.
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

0 comments on commit ea8551d

Please sign in to comment.