Repository: gakonst/ethers-rs
Pull request #378 (2021-08-14 13:36:22)

feat: generate rust structs from solidity JSON ABI
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
Currently, parameters of solidity functions that are solidity structures are generated to rust tuples, which then requires some extra boilerplate code if the user wants to use rust structs within the ethers program.

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
Since solidity 0.5.11 the solidity compiler adds an extra fields `internalType` to the inputs/outputs objects in the JSON ABI, which also contains structs with their qualified path (`struct SomeLibrary.StructName`). We can use this to essentially turn the tuple into a rust struct.

However this is not trivial because `ethabi` strips the internalType field entirely (https://github.com/rust-ethereum/ethabi/issues/230), therefor we need to parse the abi string again but keep the internalType. I made a minimal representation auf the abi (`RawAbi`) which supports that.

Next challenge are nested structs, again since the ethabi totally removes any information about the internaltype, we need to do additional work. Essentially we start with the structs that are used as inputs and outputs and check their fields recursively for structs. Since all structs boil down to elementary type, we will eventually end up with structs that only use elementary types. 
This tedious step is necessary, so that we don't end up with lots of struct duplicates.

- [x] turn JSON ABI tuples into rust structs
- [x] use them as input parameters for the corresponding contract calls
- [ ] use them as return types for the corresponding contract calls
- [ ] test some edge cases, like name collisions `LibraryA.Point` `LibraryB.Point`


<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 658cfbcc..f45666bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -846,6 +846,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "reqwest",
+ "serde",
  "serde_json",
  "syn",
  "url",
diff --git a/README.md b/README.md
index c3482271..e4e2e8af 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,7 @@ built with async/await and std futures from the ground up.
 
 - [Yield Liquidator](https://github.com/yieldprotocol/yield-liquidator/): Liquidator for Yield Protocol
 - [MEV Inspect](https://github.com/flashbots/mev-inspect-rs/): Miner Extractable Value inspector
+- [Ethers Flashbots](https://github.com/onbjerg/ethers-flashbots): Ethers middleware for [Flashbots](https://docs.flashbots.net)
 - [Ethers Fireblocks](https://github.com/gakonst/ethers-fireblocks): Ethers middleware and signer for [Fireblocks](https://fireblocks.io)' API
 - [Celo Threshold BLS DKG](https://github.com/celo-org/celo-threshold-bls-rs/): CLI for using Celo as a data availability network for the Joint-Feldman BLS DKG
 - [Celo Plumo Prover](https://github.com/celo-org/plumo-prover): Creates Celo's ultralight client proof from on-chain data
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index e72a81ad..254f71c2 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -21,7 +21,7 @@ serde_json = { version = "1.0.64", default-features = false }
 thiserror = { version = "1.0.26", default-features = false }
 once_cell = { version = "1.8.0", default-features = false }
 pin-project = {version = "1.0.7", default-features = false }
-futures-util = { version = "0.3.16", default-features = false }
+futures-util = { version = "0.3.16" }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 
 [dev-dependencies]
diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml
index 9da44463..27e2b86b 100644
--- a/ethers-contract/ethers-contract-abigen/Cargo.toml
+++ b/ethers-contract/ethers-contract-abigen/Cargo.toml
@@ -19,6 +19,7 @@ quote = "1.0"
 syn = "1.0.12"
 url = "2.1"
 serde_json = "1.0.61"
+serde = { version = "1.0.124", features = ["derive"] }
 hex = { version = "0.4.2", default-features = false, features = ["std"] }
 reqwest = { version = "0.11.3", features = ["blocking"] }
 once_cell = { version = "1.8.0", default-features = false }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index bf0be544..7ea18626 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -7,6 +7,8 @@ mod types;
 
 use super::util;
 use super::Abigen;
+use crate::contract::structs::InternalStructs;
+use crate::rawabi::RawAbi;
 use anyhow::{anyhow, Context as _, Result};
 use ethers_core::abi::AbiParser;
 use ethers_core::{
@@ -30,6 +32,9 @@ pub(crate) struct Context {
     /// The parser used for human readable format
     abi_parser: AbiParser,
 
+    /// Contains all the solidity structs extracted from the JSON ABI.
+    internal_structs: InternalStructs,
+
     /// Was the ABI in human readable format?
     human_readable: bool,
 
@@ -118,6 +123,14 @@ impl Context {
             (abi_parser.parse_str(&abi_str)?, true)
         };
 
+        // try to extract all the solidity structs from the normal JSON ABI
+        // we need to parse the json abi again because we need the internalType fields which are omitted by ethabi.
+        let internal_structs = (!human_readable)
+            .then(|| serde_json::from_str::<RawAbi>(&abi_str).ok())
+            .flatten()
+            .map(InternalStructs::new)
+            .unwrap_or_default();
+
         let contract_name = util::ident(&args.contract_name);
 
         // NOTE: We only check for duplicate signatures here, since if there are
@@ -146,6 +159,7 @@ impl Context {
             human_readable,
             abi_str: Literal::string(&abi_str),
             abi_parser,
+            internal_structs,
             contract_name,
             method_aliases,
             event_derives,
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index dbb8e3a2..f328b71d 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -22,40 +22,107 @@ impl Context {
             .flatten()
             .map(|function| {
                 let signature = function.abi_signature();
-                expand_function(function, aliases.remove(&signature))
+                self.expand_function(function, aliases.remove(&signature))
                     .with_context(|| format!("error expanding function '{}'", signature))
             })
             .collect::<Result<Vec<_>>>()?;
 
         Ok(quote! { #( #functions )* })
     }
-}
 
-#[allow(unused)]
-fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
-    let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case()));
-    let selector = expand_selector(function.selector());
+    fn expand_inputs_call_arg_with_structs(
+        &self,
+        fun: &Function,
+    ) -> Result<(TokenStream, TokenStream)> {
+        let mut args = Vec::with_capacity(fun.inputs.len());
+        let mut call_args = Vec::with_capacity(fun.inputs.len());
+        for (i, param) in fun.inputs.iter().enumerate() {
+            let name = util::expand_input_name(i, &param.name);
+            let ty = self.expand_input_param(fun, &param.name, &param.kind)?;
+            args.push(quote! { #name: #ty });
+            let call_arg = match param.kind {
+                // this is awkward edge case where the function inputs are a single struct
+                // we need to force this argument into a tuple so it gets expanded to `((#name,))`
+                // this is currently necessary because internally `flatten_tokens` is called which removes the outermost `tuple` level
+                // and since `((#name))` is not a rust tuple it doesn't get wrapped into another tuple that will be peeled off by `flatten_tokens`
+                ParamType::Tuple(_) if fun.inputs.len() == 1 => {
+                    // make sure the tuple gets converted to `Token::Tuple`
+                    quote! {(#name,)}
+                }
+                _ => name,
+            };
+            call_args.push(call_arg);
+        }
+        let args = quote! { #( , #args )* };
+        let call_args = match call_args.len() {
+            0 => quote! { () },
+            1 => quote! { #( #call_args )* },
+            _ => quote! { ( #(#call_args, )* ) },
+        };
+
+        Ok((args, call_args))
+    }
+
+    fn expand_input_param(
+        &self,
+        fun: &Function,
+        param: &str,
+        kind: &ParamType,
+    ) -> Result<TokenStream> {
+        match kind {
+            ParamType::Array(ty) => {
+                let ty = self.expand_input_param(fun, param, ty)?;
+                Ok(quote! {
+                    ::std::vec::Vec<#ty>
+                })
+            }
+            ParamType::FixedArray(ty, size) => {
+                let ty = self.expand_input_param(fun, param, ty)?;
+                let size = *size;
+                Ok(quote! {[#ty; #size]})
+            }
+            ParamType::Tuple(_) => {
+                let ty = if let Some(rust_struct_name) = self
+                    .internal_structs
+                    .get_function_input_struct_type(&fun.name, param)
+                {
+                    let ident = util::ident(rust_struct_name);
+                    quote! {#ident}
+                } else {
+                    types::expand(kind)?
+                };
+                Ok(ty)
+            }
+            _ => types::expand(kind),
+        }
+    }
 
-    let input = expand_inputs(&function.inputs)?;
+    #[allow(unused)]
+    fn expand_function(&self, function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
+        let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case()));
+        let selector = expand_selector(function.selector());
 
-    let outputs = expand_fn_outputs(&function.outputs)?;
+        // TODO use structs
+        let outputs = expand_fn_outputs(&function.outputs)?;
 
-    let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> };
+        let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> };
 
-    let arg = expand_inputs_call_arg(&function.inputs);
-    let doc = util::expand_doc(&format!(
-        "Calls the contract's `{}` (0x{}) function",
-        function.name,
-        hex::encode(function.selector())
-    ));
-    Ok(quote! {
+        let (input, arg) = self.expand_inputs_call_arg_with_structs(function)?;
 
-        #doc
-        pub fn #name(&self #input) -> #result {
-            self.0.method_hash(#selector, #arg)
-                .expect("method not found (this should never happen)")
-        }
-    })
+        let doc = util::expand_doc(&format!(
+            "Calls the contract's `{}` (0x{}) function",
+            function.name,
+            hex::encode(function.selector())
+        ));
+        Ok(quote! {
+
+            #doc
+            pub fn #name(&self #input) -> #result {
+                self.0.method_hash(#selector, #arg)
+                    .expect("method not found (this should never happen)")
+            }
+        })
+    }
 }
 
 // converts the function params to name/type pairs
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index cacb8e16..cc6bb164 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -1,13 +1,21 @@
 //! Methods for expanding structs
+use std::collections::{HashMap, VecDeque};
+
 use anyhow::{Context as _, Result};
 use inflector::Inflector;
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 
-use ethers_core::abi::{struct_def::FieldType, ParamType};
+use ethers_core::abi::{
+    param_type::Reader,
+    struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType},
+    ParamType, SolStruct,
+};
 
 use crate::contract::{types, Context};
+use crate::rawabi::{Component, RawAbi};
 use crate::util;
+use std::any::Any;
 
 impl Context {
     /// Generate corresponding types for structs parsed from a human readable ABI
@@ -16,8 +24,102 @@ impl Context {
     /// in fact present in the `AbiParser`, this is sound because `AbiParser::parse` would have
     /// failed already
     pub fn abi_structs(&self) -> Result<TokenStream> {
-        let mut structs = Vec::with_capacity(self.abi_parser.structs.len());
-        for (name, sol_struct) in &self.abi_parser.structs {
+        if self.human_readable {
+            self.gen_human_readable_structs()
+        } else {
+            self.gen_internal_structs()
+        }
+    }
+
+    /// 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)?);
+        }
+        Ok(structs)
+    }
+
+    /// Expand all structs parsed from the internal types of the JSON ABI
+    fn expand_internal_struct(
+        &self,
+        name: &str,
+        sol_struct: &SolStruct,
+        tuple: ParamType,
+    ) -> Result<TokenStream> {
+        let mut fields = 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) => {
+                    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 });
+                }
+                FieldType::Mapping(_) => {
+                    return Err(anyhow::anyhow!(
+                        "Mapping types in struct `{}` are not supported {:?}",
+                        name,
+                        field
+                    ));
+                }
+            }
+        }
+
+        let sig = if let ParamType::Tuple(ref tokens) = tuple {
+            tokens
+                .iter()
+                .map(|kind| kind.to_string())
+                .collect::<Vec<_>>()
+                .join(",")
+        } else {
+            "".to_string()
+        };
+
+        let abi_signature = format!("{}({})", name, sig,);
+
+        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),*};
+
+        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() {
@@ -29,44 +131,19 @@ impl Context {
                         fields.push(quote! { pub #field_name: #ty });
                     }
                     FieldType::Struct(struct_ty) => {
-                        let ty = util::ident(struct_ty.name());
+                        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(struct_ty.name())
-                            .context(format!("No types found for {}", struct_ty.name()))?
-                            .clone();
-                        param_types.push(ParamType::Tuple(tuple));
-                    }
-                    FieldType::StructArray(struct_ty) => {
-                        let ty = util::ident(struct_ty.name());
-                        fields.push(quote! { pub #field_name: ::std::vec::Vec<#ty> });
-
-                        let tuple = self
-                            .abi_parser
-                            .struct_tuples
-                            .get(struct_ty.name())
-                            .context(format!("No types found for {}", struct_ty.name()))?
+                            .get(name)
+                            .context(format!("No types found for {}", name))?
                             .clone();
-                        param_types.push(ParamType::Array(Box::new(ParamType::Tuple(tuple))));
-                    }
-                    FieldType::FixedStructArray(struct_ty, len) => {
-                        let ty = util::ident(struct_ty.name());
-                        let size = Literal::usize_unsuffixed(*len);
-                        fields.push(quote! { pub #field_name: [#ty; #size] });
+                        let tuple = ParamType::Tuple(tuple);
 
-                        let tuple = self
-                            .abi_parser
-                            .struct_tuples
-                            .get(struct_ty.name())
-                            .context(format!("No types found for {}", struct_ty.name()))?
-                            .clone();
-                        param_types.push(ParamType::FixedArray(
-                            Box::new(ParamType::Tuple(tuple)),
-                            *len,
-                        ));
+                        param_types.push(struct_ty.as_param(tuple));
                     }
                     FieldType::Mapping(_) => {
                         return Err(anyhow::anyhow!(
@@ -96,14 +173,349 @@ impl Context {
             let derives = &self.event_derives;
             let derives = quote! {#(#derives),*};
 
-            structs.push(quote! {
-                #abi_signature_doc
-                #[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)]
-                pub struct #name {
-                    #( #fields ),*
+            structs.extend(quote! {
+            #abi_signature_doc
+            #[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)]
+            pub struct #name {
+                #( #fields ),*
+            }
+        });
+        }
+        Ok(structs)
+    }
+}
+
+/// Helper to match `ethabi::Param`s with structs and nested structs
+///
+/// This is currently used to get access to all the unique solidity structs used as function in/output until `ethabi` supports it as well.
+#[derive(Debug, Clone, Default)]
+pub struct InternalStructs {
+    /// All unique internal types that are function inputs or outputs
+    top_level_internal_types: HashMap<String, Component>,
+
+    /// (function name, param name) -> struct which are the identifying properties we get the name from ethabi.
+    function_params: HashMap<(String, String), String>,
+
+    /// (function name) -> Vec<structs> all structs the function returns
+    outputs: HashMap<String, Vec<String>>,
+
+    /// All the structs extracted from the abi with their identifier as key
+    structs: HashMap<String, SolStruct>,
+
+    /// solidity structs as tuples
+    struct_tuples: HashMap<String, ParamType>,
+
+    /// Contains the names for the rust types (id -> rust type name)
+    rust_type_names: HashMap<String, String>,
+}
+
+impl InternalStructs {
+    pub fn new(abi: RawAbi) -> Self {
+        let mut top_level_internal_types = HashMap::new();
+        let mut function_params = HashMap::new();
+        let mut outputs = HashMap::new();
+        let mut structs = HashMap::new();
+        for item in abi
+            .into_iter()
+            .filter(|item| item.type_field == "constructor" || item.type_field == "function")
+        {
+            if let Some(name) = item.name {
+                for input in item.inputs {
+                    if let Some(ty) = input
+                        .internal_type
+                        .as_deref()
+                        .filter(|ty| ty.starts_with("struct "))
+                        .map(struct_type_identifier)
+                    {
+                        function_params.insert((name.clone(), input.name.clone()), ty.to_string());
+                        top_level_internal_types.insert(ty.to_string(), input);
+                    }
+                }
+                let mut output_structs = Vec::new();
+                for output in item.outputs {
+                    if let Some(ty) = output
+                        .internal_type
+                        .as_deref()
+                        .filter(|ty| ty.starts_with("struct "))
+                        .map(struct_type_identifier)
+                    {
+                        output_structs.push(ty.to_string());
+                        top_level_internal_types.insert(ty.to_string(), output);
+                    }
                 }
-            });
+                outputs.insert(name, output_structs);
+            }
+        }
+
+        // turn each top level internal type (function input/output) and their nested types
+        // into a struct will create all structs
+        for component in top_level_internal_types.values() {
+            insert_structs(&mut structs, component);
         }
-        Ok(quote! {#( #structs )*})
+
+        // determine the `ParamType` representation of each struct
+        let struct_tuples = resolve_struct_tuples(&structs);
+
+        // name -> (id, projections)
+        let mut type_names: HashMap<String, (String, Vec<String>)> =
+            HashMap::with_capacity(structs.len());
+        for id in structs.keys() {
+            let name = struct_type_name(id).to_pascal_case();
+            let projections = struct_type_projections(id);
+            insert_rust_type_name(&mut type_names, name, projections, id.clone());
+        }
+
+        Self {
+            top_level_internal_types,
+            function_params,
+            outputs,
+            structs,
+            struct_tuples,
+            rust_type_names: type_names
+                .into_iter()
+                .map(|(rust_name, (id, _))| (id, rust_name))
+                .collect(),
+        }
+    }
+
+    /// Returns the name of the rust type that will be generated if the given input is a struct
+    /// NOTE: this does not account for arrays or fixed arrays
+    pub fn get_function_input_struct_type(&self, function: &str, input: &str) -> Option<&str> {
+        let key = (function.to_string(), input.to_string());
+        self.function_params
+            .get(&key)
+            .and_then(|id| self.rust_type_names.get(id))
+            .map(String::as_str)
+    }
+}
+
+/// 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`.
+fn insert_rust_type_name(
+    type_names: &mut HashMap<String, (String, Vec<String>)>,
+    mut name: String,
+    mut projections: Vec<String>,
+    id: String,
+) {
+    if let Some((other_id, mut other_projections)) = type_names.remove(&name) {
+        let mut other_name = name.clone();
+        // name collision `A.name` `B.name`, rename to `AName`, `BName`
+        if !other_projections.is_empty() {
+            other_name = format!(
+                "{}{}",
+                other_projections.remove(0).to_pascal_case(),
+                other_name
+            );
+        }
+        insert_rust_type_name(type_names, other_name, other_projections, other_id);
+
+        if !projections.is_empty() {
+            name = format!("{}{}", projections.remove(0).to_pascal_case(), name);
+        }
+        insert_rust_type_name(type_names, name, projections, id);
+    } else {
+        type_names.insert(name, (id, projections));
+    }
+}
+
+/// Tries to determine the `ParamType::Tuple` for every struct.
+///
+/// If a structure has nested structures, these must be determined first, essentially starting with structures consisting of only elementary types before moving on to higher level structures, for example `Proof {point: Point}, Point {x:int, y:int}` start by converting Point into a tuple of `x` and `y` and then substituting `point` with this within `Proof`.
+fn resolve_struct_tuples(all_structs: &HashMap<String, SolStruct>) -> HashMap<String, ParamType> {
+    let mut params = HashMap::new();
+    let mut structs: VecDeque<_> = all_structs.iter().collect();
+
+    // keep track of how often we retried nested structs
+    let mut sequential_retries = 0;
+    'outer: while let Some((id, ty)) = structs.pop_front() {
+        if sequential_retries > structs.len() {
+            break;
+        }
+        if let Some(tuple) = ty.as_tuple() {
+            params.insert(id.to_string(), tuple);
+        } else {
+            // try to substitute all nested struct types with their `ParamTypes`
+            let mut struct_params = Vec::with_capacity(ty.fields.len());
+            for field in ty.fields() {
+                match field.ty {
+                    FieldType::Elementary(ref param) => {
+                        struct_params.push(param.clone());
+                    }
+                    FieldType::Struct(ref field_ty) => {
+                        // nested struct
+                        let ty_id = field_ty.identifier();
+                        if let Some(nested) = params.get(&ty_id).cloned() {
+                            match field_ty {
+                                StructFieldType::Type(_) => struct_params.push(nested),
+                                StructFieldType::Array(_) => {
+                                    struct_params.push(ParamType::Array(Box::new(nested)));
+                                }
+                                StructFieldType::FixedArray(_, size) => {
+                                    struct_params
+                                        .push(ParamType::FixedArray(Box::new(nested), *size));
+                                }
+                            }
+                        } else {
+                            // struct field needs to be resolved first
+                            structs.push_back((id, ty));
+                            sequential_retries += 1;
+                            continue 'outer;
+                        }
+                    }
+                    _ => {
+                        unreachable!("mapping types are unsupported")
+                    }
+                }
+            }
+            params.insert(id.to_string(), ParamType::Tuple(struct_params));
+        }
+
+        // we resolved a new param, so we can try all again
+        sequential_retries = 0;
+    }
+    params
+}
+
+/// turns the tuple component into a struct if it's still missing in the map, including all inner structs
+fn insert_structs(structs: &mut HashMap<String, SolStruct>, tuple: &Component) {
+    if let Some(internal_ty) = tuple.internal_type.as_ref() {
+        let ident = struct_type_identifier(internal_ty);
+        if structs.contains_key(ident) {
+            return;
+        }
+        if let Some(fields) = tuple
+            .components
+            .iter()
+            .map(|f| {
+                Reader::read(&f.type_field)
+                    .ok()
+                    .and_then(|kind| field(structs, f, kind))
+            })
+            .collect::<Option<Vec<_>>>()
+        {
+            let s = SolStruct {
+                name: ident.to_string(),
+                fields,
+            };
+            structs.insert(ident.to_string(), s);
+        }
+    }
+}
+
+/// Determines the type of the field component
+fn field(
+    structs: &mut HashMap<String, SolStruct>,
+    field_component: &Component,
+    kind: ParamType,
+) -> Option<FieldDeclaration> {
+    match kind {
+        ParamType::Array(ty) => {
+            let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
+            match ty {
+                FieldType::Elementary(kind) => {
+                    // this arm represents all the elementary types like address, uint...
+                    Some(FieldDeclaration::new(
+                        field_component.name.clone(),
+                        FieldType::Elementary(ParamType::Array(Box::new(kind))),
+                    ))
+                }
+                FieldType::Struct(ty) => Some(FieldDeclaration::new(
+                    field_component.name.clone(),
+                    FieldType::Struct(StructFieldType::Array(Box::new(ty))),
+                )),
+                _ => {
+                    unreachable!("no mappings types support as function inputs or outputs")
+                }
+            }
+        }
+        ParamType::FixedArray(ty, size) => {
+            let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
+            match ty {
+                FieldType::Elementary(kind) => {
+                    // this arm represents all the elementary types like address, uint...
+                    Some(FieldDeclaration::new(
+                        field_component.name.clone(),
+                        FieldType::Elementary(ParamType::FixedArray(Box::new(kind), size)),
+                    ))
+                }
+                FieldType::Struct(ty) => Some(FieldDeclaration::new(
+                    field_component.name.clone(),
+                    FieldType::Struct(StructFieldType::FixedArray(Box::new(ty), size)),
+                )),
+                _ => {
+                    unreachable!("no mappings types support as function inputs or outputs")
+                }
+            }
+        }
+        ParamType::Tuple(_) => {
+            insert_structs(structs, field_component);
+            let internal_type = field_component.internal_type.as_ref()?;
+            let ty = struct_type_identifier(internal_type);
+            // split the identifier into the name and all projections:
+            // `A.B.C.name` -> name, [A,B,C]
+            let mut idents = ty.rsplit('.');
+            let name = idents.next().unwrap().to_string();
+            let projections = idents.rev().map(str::to_string).collect();
+            Some(FieldDeclaration::new(
+                field_component.name.clone(),
+                FieldType::Struct(StructFieldType::Type(StructType::new(name, projections))),
+            ))
+        }
+        elementary => Some(FieldDeclaration::new(
+            field_component.name.clone(),
+            FieldType::Elementary(elementary),
+        )),
+    }
+}
+
+/// `struct Pairing.G2Point[]` -> `G2Point`
+fn struct_type_name(name: &str) -> &str {
+    struct_type_identifier(name).rsplit('.').next().unwrap()
+}
+
+/// `Pairing.G2Point` -> `Pairing.G2Point`
+fn struct_type_identifier(name: &str) -> &str {
+    name.trim_start_matches("struct ")
+        .split('[')
+        .next()
+        .unwrap()
+}
+
+/// `struct Pairing.Nested.G2Point[]` -> `[Pairing, Nested]`
+fn struct_type_projections(name: &str) -> Vec<String> {
+    let id = struct_type_identifier(name);
+    let mut iter = id.rsplit('.');
+    iter.next();
+    iter.rev().map(str::to_string).collect()
+}
+
+/// Expands to the rust struct type
+fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
+    match struct_ty {
+        StructFieldType::Type(ty) => {
+            let ty = util::ident(ty.name());
+            quote! {#ty}
+        }
+        StructFieldType::Array(ty) => {
+            let ty = expand_struct_type(&*ty);
+            quote! {::std::vec::Vec<#ty>}
+        }
+        StructFieldType::FixedArray(ty, size) => {
+            let ty = expand_struct_type(&*ty);
+            quote! { [#ty; #size]}
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn can_determine_structs() {
+        const VERIFIER_ABI: &str =
+            include_str!("../../../tests/solidity-contracts/verifier_abi.json");
+        let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
+
+        let internal = InternalStructs::new(abi);
+        dbg!(internal.rust_type_names);
     }
 }
diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs
index 716ac85b..3d3a159e 100644
--- a/ethers-contract/ethers-contract-abigen/src/lib.rs
+++ b/ethers-contract/ethers-contract-abigen/src/lib.rs
@@ -15,6 +15,7 @@ mod test_macros;
 mod contract;
 use contract::Context;
 
+pub mod rawabi;
 mod rustfmt;
 mod source;
 mod util;
diff --git a/ethers-contract/ethers-contract-abigen/src/rawabi.rs b/ethers-contract/ethers-contract-abigen/src/rawabi.rs
new file mode 100644
index 00000000..fc04f49d
--- /dev/null
+++ b/ethers-contract/ethers-contract-abigen/src/rawabi.rs
@@ -0,0 +1,48 @@
+//! This is a basic representation of a contract ABI that does no post processing but contains the raw content of the ABI.
+
+#![allow(missing_docs)]
+use serde::{Deserialize, Serialize};
+
+/// Contract ABI as a list of items where each item can be a function, constructor or event
+pub type RawAbi = Vec<Item>;
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Item {
+    #[serde(default)]
+    pub inputs: Vec<Component>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub state_mutability: Option<String>,
+    #[serde(rename = "type")]
+    pub type_field: String,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub name: Option<String>,
+    #[serde(default)]
+    pub outputs: Vec<Component>,
+}
+
+/// Either
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Component {
+    #[serde(
+        rename = "internalType",
+        default,
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub internal_type: Option<String>,
+    pub name: String,
+    #[serde(rename = "type")]
+    pub type_field: String,
+    #[serde(default)]
+    pub components: Vec<Component>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn can_parse_raw_abi() {
+        const VERIFIER_ABI: &str = include_str!("../../tests/solidity-contracts/verifier_abi.json");
+        let _ = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
+    }
+}
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 10e2f09a..c3e03f56 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -33,7 +33,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] }
 
 # async
 tokio = { version = "1.5", default-features = false, optional = true}
-futures-util = { version = "0.3.16", default-features = false, optional = true, features = ["async-await-macro"]}
+futures-util = { version = "0.3.16", optional = true }
 
 [dev-dependencies]
 ethers = { version = "0.4.0", path = "../ethers" }
@@ -42,7 +42,7 @@ serde_json = { version = "1.0.64", default-features = false }
 bincode = { version = "1.3.3", default-features = false }
 once_cell = { version = "1.8.0" }
 hex-literal = "0.3.3"
-futures-util = { version = "0.3.16", default-features = false }
+futures-util = { version = "0.3.16" }
 
 [features]
 celo = [] # celo support extends the transaction format with extra fields
diff --git a/ethers-core/src/abi/human_readable.rs b/ethers-core/src/abi/human_readable.rs
index a88afb97..8b0de3ee 100644
--- a/ethers-core/src/abi/human_readable.rs
+++ b/ethers-core/src/abi/human_readable.rs
@@ -110,26 +110,7 @@ impl AbiParser {
                     FieldType::Elementary(param) => tuple.push(param.clone()),
                     FieldType::Struct(ty) => {
                         if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
-                            tuple.push(ParamType::Tuple(param))
-                        } else {
-                            resolved = false;
-                            break;
-                        }
-                    }
-                    FieldType::StructArray(ty) => {
-                        if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
-                            tuple.push(ParamType::Array(Box::new(ParamType::Tuple(param))))
-                        } else {
-                            resolved = false;
-                            break;
-                        }
-                    }
-                    FieldType::FixedStructArray(ty, size) => {
-                        if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
-                            tuple.push(ParamType::FixedArray(
-                                Box::new(ParamType::Tuple(param)),
-                                *size,
-                            ))
+                            tuple.push(ty.as_param(ParamType::Tuple(param)))
                         } else {
                             resolved = false;
                             break;
@@ -331,13 +312,10 @@ impl AbiParser {
                     .map(ParamType::Tuple)
                     .ok_or_else(|| format_err!("Unknown struct `{}`", struct_ty.name()))?;
 
-                match field {
-                    FieldType::Struct(_) => Ok(tuple),
-                    FieldType::StructArray(_) => Ok(ParamType::Array(Box::new(tuple))),
-                    FieldType::FixedStructArray(_, size) => {
-                        Ok(ParamType::FixedArray(Box::new(tuple), size))
-                    }
-                    _ => bail!("Expected struct type"),
+                if let Some(field) = field.as_struct() {
+                    Ok(field.as_param(tuple))
+                } else {
+                    bail!("Expected struct type")
                 }
             } else {
                 bail!("Failed determine event type `{}`", type_str)
diff --git a/ethers-core/src/abi/struct_def.rs b/ethers-core/src/abi/struct_def.rs
index 74b836f4..7c2ec6b4 100644
--- a/ethers-core/src/abi/struct_def.rs
+++ b/ethers-core/src/abi/struct_def.rs
@@ -6,11 +6,15 @@ use crate::abi::{param_type::Reader, ParamType};
 /// A field declaration inside a struct
 #[derive(Debug, Clone, PartialEq)]
 pub struct FieldDeclaration {
-    name: String,
-    ty: FieldType,
+    pub name: String,
+    pub ty: FieldType,
 }
 
 impl FieldDeclaration {
+    pub fn new(name: String, ty: FieldType) -> Self {
+        Self { name, ty }
+    }
+
     pub fn name(&self) -> &str {
         &self.name
     }
@@ -24,13 +28,11 @@ impl FieldDeclaration {
 #[derive(Debug, Clone, PartialEq)]
 pub enum FieldType {
     /// Represents elementary types, see [`ParamType`]
+    ///
+    /// Note: tuples will be treated as rust tuples
     Elementary(ParamType),
     /// A non elementary type field, treated as user defined struct
     Struct(StructFieldType),
-    // Array of user defined type
-    StructArray(StructFieldType),
-    // Array with fixed size of user defined type
-    FixedStructArray(StructFieldType, usize),
     /// Mapping
     Mapping(Box<MappingType>),
 }
@@ -42,9 +44,7 @@ impl FieldType {
 
     pub(crate) fn as_struct(&self) -> Option<&StructFieldType> {
         match self {
-            FieldType::Struct(s)
-            | FieldType::StructArray(s)
-            | FieldType::FixedStructArray(s, _) => Some(s),
+            FieldType::Struct(s) => Some(s),
             _ => None,
         }
     }
@@ -72,8 +72,8 @@ pub struct StructFieldDeclaration {
 
 /// How the type of a struct field is referenced
 #[derive(Debug, Clone, PartialEq)]
-pub struct StructFieldType {
-    /// The name of the struct
+pub struct StructType {
+    /// The name of the struct (or rather the name of the rust type)
     name: String,
     /// All previous projections up until the name
     ///
@@ -81,10 +81,63 @@ pub struct StructFieldType {
     projections: Vec<String>,
 }
 
-impl StructFieldType {
+impl StructType {
+    pub fn new(name: String, projections: Vec<String>) -> Self {
+        Self { name, projections }
+    }
+
     pub fn name(&self) -> &str {
         &self.name
     }
+}
+
+/// Represents the type of a field in a struct
+#[derive(Debug, Clone, PartialEq)]
+pub enum StructFieldType {
+    /// A non elementary type field, represents a user defined struct
+    Type(StructType),
+    // Array of user defined type
+    Array(Box<StructFieldType>),
+    // Array with fixed size of user defined type
+    FixedArray(Box<StructFieldType>, usize),
+}
+
+impl StructFieldType {
+    pub fn name(&self) -> &str {
+        match self {
+            StructFieldType::Type(ty) => &ty.name,
+            StructFieldType::Array(ty) => ty.name(),
+            StructFieldType::FixedArray(ty, _) => ty.name(),
+        }
+    }
+
+    pub fn projections(&self) -> &[String] {
+        match self {
+            StructFieldType::Type(ty) => &ty.projections,
+            StructFieldType::Array(ty) => ty.projections(),
+            StructFieldType::FixedArray(ty, _) => ty.projections(),
+        }
+    }
+
+    pub fn identifier(&self) -> String {
+        let name = self.name();
+        let path = self.projections().join(".");
+        if path.is_empty() {
+            name.to_string()
+        } else {
+            format!("{}.{}", path, name)
+        }
+    }
+
+    pub fn as_param(&self, tuple: ParamType) -> ParamType {
+        match self {
+            StructFieldType::Type(_) => tuple,
+            StructFieldType::Array(ty) => ty.as_param(ParamType::Array(Box::new(tuple))),
+            StructFieldType::FixedArray(ty, size) => {
+                ty.as_param(ParamType::FixedArray(Box::new(tuple), *size))
+            }
+        }
+    }
 
     /// Parse a struct field declaration
     ///
@@ -97,10 +150,10 @@ impl StructFieldType {
             let mut chars = input.chars();
             match chars.next() {
                 None => {
-                    return Ok(FieldType::Struct(StructFieldType {
+                    return Ok(FieldType::Struct(StructFieldType::Type(StructType {
                         name: ty,
                         projections,
-                    }))
+                    })))
                 }
                 Some(' ') | Some('\t') | Some('[') => {
                     // array
@@ -118,18 +171,23 @@ impl StructFieldType {
                                 }
                             }
                             Some(']') => {
-                                let ty = StructFieldType {
+                                let ty = StructType {
                                     name: ty,
                                     projections,
                                 };
 
                                 return if size.is_empty() {
-                                    Ok(FieldType::StructArray(ty))
+                                    Ok(FieldType::Struct(StructFieldType::Array(Box::new(
+                                        StructFieldType::Type(ty),
+                                    ))))
                                 } else {
                                     let size = size.parse().map_err(|_| {
                                         format_err!("Illegal array size `{}` at `{}`", size, input)
                                     })?;
-                                    Ok(FieldType::FixedStructArray(ty, size))
+                                    Ok(FieldType::Struct(StructFieldType::FixedArray(
+                                        Box::new(StructFieldType::Type(ty)),
+                                        size,
+                                    )))
                                 };
                             }
                             Some(c) => {
@@ -157,8 +215,8 @@ impl StructFieldType {
 /// Represents a solidity struct
 #[derive(Debug, Clone, PartialEq)]
 pub struct SolStruct {
-    name: String,
-    fields: Vec<FieldDeclaration>,
+    pub name: String,
+    pub fields: Vec<FieldDeclaration>,
 }
 
 impl SolStruct {
@@ -223,6 +281,19 @@ impl SolStruct {
     pub fn fields(&self) -> &Vec<FieldDeclaration> {
         &self.fields
     }
+
+    /// If the struct only consists of elementary fields, this will return `ParamType::Tuple` with all those fields
+    pub fn as_tuple(&self) -> Option<ParamType> {
+        let mut params = Vec::with_capacity(self.fields.len());
+        for field in self.fields() {
+            if let FieldType::Elementary(ref param) = field.ty {
+                params.push(param.clone())
+            } else {
+                return None;
+            }
+        }
+        Some(ParamType::Tuple(params))
+    }
 }
 
 /// Strips the identifier of field declaration from the input and returns it
@@ -386,10 +457,10 @@ mod tests {
                     },
                     FieldDeclaration {
                         name: "_other".to_string(),
-                        ty: FieldType::Struct(StructFieldType {
+                        ty: FieldType::Struct(StructFieldType::Type(StructType {
                             name: "Inner".to_string(),
                             projections: vec!["Some".to_string(), "Other".to_string()]
-                        }),
+                        })),
                     },
                 ],
             }
diff --git a/ethers-core/src/types/signature.rs b/ethers-core/src/types/signature.rs
index bf21b14b..959c0d33 100644
--- a/ethers-core/src/types/signature.rs
+++ b/ethers-core/src/types/signature.rs
@@ -1,6 +1,6 @@
 // Code adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/api/accounts.rs
 use crate::{
-    types::{Address, H256},
+    types::{Address, H256, U256},
     utils::hash_message,
 };
 
@@ -55,9 +55,9 @@ pub enum RecoveryMessage {
 /// An ECDSA signature
 pub struct Signature {
     /// R value
-    pub r: H256,
+    pub r: U256,
     /// S Value
-    pub s: H256,
+    pub s: U256,
     /// V value in 'Electrum' notation.
     pub v: u64,
 }
@@ -118,8 +118,12 @@ impl Signature {
     fn as_signature(&self) -> Result<(RecoverableSignature, RecoveryId), SignatureError> {
         let recovery_id = self.recovery_id()?;
         let signature = {
-            let gar: &GenericArray<u8, U32> = GenericArray::from_slice(self.r.as_bytes());
-            let gas: &GenericArray<u8, U32> = GenericArray::from_slice(self.s.as_bytes());
+            let mut r_bytes = [0u8; 32];
+            let mut s_bytes = [0u8; 32];
+            self.r.to_big_endian(&mut r_bytes);
+            self.s.to_big_endian(&mut s_bytes);
+            let gar: &GenericArray<u8, U32> = GenericArray::from_slice(&r_bytes);
+            let gas: &GenericArray<u8, U32> = GenericArray::from_slice(&s_bytes);
             let sig = K256Signature::from_scalars(*gar, *gas)?;
             RecoverableSignature::new(&sig, recovery_id)?
         };
@@ -163,8 +167,8 @@ impl<'a> TryFrom<&'a [u8]> for Signature {
         }
 
         let v = bytes[64];
-        let r = H256::from_slice(&bytes[0..32]);
-        let s = H256::from_slice(&bytes[32..64]);
+        let r = U256::from_big_endian(&bytes[0..32]);
+        let s = U256::from_big_endian(&bytes[32..64]);
 
         Ok(Signature { r, s, v: v.into() })
     }
@@ -183,8 +187,12 @@ impl FromStr for Signature {
 impl From<&Signature> for [u8; 65] {
     fn from(src: &Signature) -> [u8; 65] {
         let mut sig = [0u8; 65];
-        sig[..32].copy_from_slice(src.r.as_bytes());
-        sig[32..64].copy_from_slice(src.s.as_bytes());
+        let mut r_bytes = [0u8; 32];
+        let mut s_bytes = [0u8; 32];
+        src.r.to_big_endian(&mut r_bytes);
+        src.s.to_big_endian(&mut s_bytes);
+        sig[..32].copy_from_slice(&r_bytes);
+        sig[32..64].copy_from_slice(&s_bytes);
         // TODO: What if we try to serialize a signature where
         // the `v` is not normalized?
         sig[64] = src.v as u8;
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index 35e1d755..1104ba68 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -22,7 +22,7 @@ ethers-signers = { version = "0.4.0", path = "../ethers-signers", default-featur
 async-trait = { version = "0.1.50", default-features = false }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 thiserror = { version = "1.0.26", default-features = false }
-futures-util = { version = "0.3.16", default-features = false }
+futures-util = { version = "0.3.16" }
 tracing = { version = "0.1.26", default-features = false }
 tracing-futures = { version = "0.2.5", default-features = false }
 
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index 518223a1..50792f99 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -27,7 +27,7 @@ auto_impl = { version = "0.4.1", default-features = false }
 
 # required for implementing stream on the filters
 futures-core = { version = "0.3.12", default-features = false }
-futures-util = { version = "0.3.16", default-features = false }
+futures-util = { version = "0.3.16" }
 futures-timer = { version = "3.0.2", default-features = false }
 futures-channel = { version = "0.3.13", default-features = false }
 pin-project = { version = "1.0.7", default-features = false }
diff --git a/ethers-signers/src/aws/utils.rs b/ethers-signers/src/aws/utils.rs
index 149b66d7..7b98ec5a 100644
--- a/ethers-signers/src/aws/utils.rs
+++ b/ethers-signers/src/aws/utils.rs
@@ -13,7 +13,7 @@ use ethers_core::{
         elliptic_curve::sec1::ToEncodedPoint,
         FieldBytes,
     },
-    types::{Address, Signature as EthSig, H256},
+    types::{Address, Signature as EthSig, U256},
     utils::keccak256,
 };
 use rusoto_kms::{GetPublicKeyResponse, SignResponse};
@@ -26,8 +26,8 @@ pub(super) fn rsig_to_ethsig(sig: &RSig) -> EthSig {
     let v = (v + 27) as u64;
     let r_bytes: FieldBytes = sig.r().into();
     let s_bytes: FieldBytes = sig.s().into();
-    let r = H256::from_slice(&r_bytes.as_slice());
-    let s = H256::from_slice(&s_bytes.as_slice());
+    let r = U256::from_big_endian(&r_bytes.as_slice());
+    let s = U256::from_big_endian(&s_bytes.as_slice());
     EthSig { r, s, v }
 }
 
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index 2579b3df..0a02294f 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -170,8 +170,8 @@ impl LedgerEthereum {
         }
 
         let v = result[0] as u64;
-        let r = H256::from_slice(&result[1..33]);
-        let s = H256::from_slice(&result[33..]);
+        let r = U256::from_big_endian(&result[1..33]);
+        let s = U256::from_big_endian(&result[33..]);
         Ok(Signature { r, s, v })
     }
 
diff --git a/ethers-signers/src/wallet/mod.rs b/ethers-signers/src/wallet/mod.rs
index 53d3a294..bf4d0876 100644
--- a/ethers-signers/src/wallet/mod.rs
+++ b/ethers-signers/src/wallet/mod.rs
@@ -16,7 +16,7 @@ use ethers_core::{
         elliptic_curve::FieldBytes,
         Secp256k1,
     },
-    types::{transaction::eip2718::TypedTransaction, Address, Signature, H256},
+    types::{transaction::eip2718::TypedTransaction, Address, Signature, H256, U256},
     utils::hash_message,
 };
 use hash::Sha256Proxy;
@@ -116,8 +116,8 @@ impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> Wallet<D> {
 
         let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
         let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
-        let r = H256::from_slice(r_bytes.as_slice());
-        let s = H256::from_slice(s_bytes.as_slice());
+        let r = U256::from_big_endian(r_bytes.as_slice());
+        let s = U256::from_big_endian(s_bytes.as_slice());
 
         Signature { r, s, v }
     }
diff --git a/ethers/examples/contract_with_abi_and_structs.rs b/ethers/examples/contract_with_abi_and_structs.rs
new file mode 100644
index 00000000..a771fb4f
--- /dev/null
+++ b/ethers/examples/contract_with_abi_and_structs.rs
@@ -0,0 +1,49 @@
+//! Main entry point for ContractMonitor
+
+use ethers::{prelude::*, utils::Ganache};
+use std::{convert::TryFrom, sync::Arc, time::Duration};
+
+abigen!(
+    VerifierContract,
+    "ethers-contract/tests/solidity-contracts/verifier_abi.json"
+);
+
+/// This example only demonstrates how to use generated structs for solidity functions that
+/// have structs as input.
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let ganache = Ganache::new().spawn();
+    let provider =
+        Provider::<Http>::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64));
+    let wallet: LocalWallet = ganache.keys()[0].clone().into();
+
+    let client = SignerMiddleware::new(provider, wallet);
+    let client = Arc::new(client);
+
+    let contract = VerifierContract::new(Address::zero(), client);
+
+    // NOTE: this is all just dummy data
+    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,
+    };
+
+    let _ = contract.verify(vec![], proof, vk);
+    Ok(())
+}


Test patch:
diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs
index 5e601724..fdf6cd8a 100644
--- a/ethers-contract/tests/abigen.rs
+++ b/ethers-contract/tests/abigen.rs
@@ -43,24 +43,33 @@ fn can_gen_structs_readable() {
     );
 }
 
-// NOTE(mattsse): There is currently a limitation with the `ethabi` crate's `Reader`
-//  that doesn't support arrays of tuples; https://github.com/gakonst/ethabi/pull/1 should fix this
-// See also https://github.com/rust-ethereum/ethabi/issues/178 and
-// https://github.com/rust-ethereum/ethabi/pull/186
+#[test]
+fn can_gen_structs_with_arrays_readable() {
+    abigen!(
+        SimpleContract,
+        r#"[
+        struct Value {address addr; string value;}
+        struct Addresses {address[] addr; string s;}
+        event ValueChanged(Value indexed old, Value newValue, Addresses[] _a)
+    ]"#,
+        event_derives(serde::Deserialize, serde::Serialize)
+    );
+    assert_eq!(
+        "ValueChanged((address,string),(address,string),(address[],string)[])",
+        ValueChangedFilter::abi_signature()
+    );
+}
 
-// #[test]
-// fn can_gen_structs_with_arrays_readable() {
-//     abigen!(
-//         SimpleContract,
-//         r#"[
-//         struct Value {address addr; string value;}
-//         struct Addresses {address[] addr; string s;}
-//         event ValueChanged(Value indexed old, Value newValue, Addresses[] _a)
-//     ]"#,
-//         event_derives(serde::Deserialize, serde::Serialize)
-//     );
-//     assert_eq!(
-//         "ValueChanged((address,string),(address,string),(address[],string)[])",
-//         ValueChangedFilter::abi_signature()
-//     );
-// }
+fn assert_tokenizeable<T: Tokenizable>() {}
+
+#[test]
+fn can_generate_internal_structs() {
+    abigen!(
+        VerifierContract,
+        "ethers-contract/tests/solidity-contracts/verifier_abi.json",
+        event_derives(serde::Deserialize, serde::Serialize)
+    );
+    assert_tokenizeable::<VerifyingKey>();
+    assert_tokenizeable::<G1Point>();
+    assert_tokenizeable::<G2Point>();
+}
diff --git a/ethers-contract/tests/solidity-contracts/verifier_abi.json b/ethers-contract/tests/solidity-contracts/verifier_abi.json
new file mode 100644
index 00000000..8721f0bd
--- /dev/null
+++ b/ethers-contract/tests/solidity-contracts/verifier_abi.json
@@ -0,0 +1,176 @@
+[
+  {
+    "inputs": [],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "input",
+        "type": "uint256[]"
+      },
+      {
+        "components": [
+          {
+            "components": [
+              {
+                "internalType": "uint256",
+                "name": "X",
+                "type": "uint256"
+              },
+              {
+                "internalType": "uint256",
+                "name": "Y",
+                "type": "uint256"
+              }
+            ],
+            "internalType": "struct Pairing.G1Point",
+            "name": "A",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256[2]",
+                "name": "X",
+                "type": "uint256[2]"
+              },
+              {
+                "internalType": "uint256[2]",
+                "name": "Y",
+                "type": "uint256[2]"
+              }
+            ],
+            "internalType": "struct Pairing.G2Point",
+            "name": "B",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256",
+                "name": "X",
+                "type": "uint256"
+              },
+              {
+                "internalType": "uint256",
+                "name": "Y",
+                "type": "uint256"
+              }
+            ],
+            "internalType": "struct Pairing.G1Point",
+            "name": "C",
+            "type": "tuple"
+          }
+        ],
+        "internalType": "struct Verifier.Proof",
+        "name": "proof",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          {
+            "components": [
+              {
+                "internalType": "uint256",
+                "name": "X",
+                "type": "uint256"
+              },
+              {
+                "internalType": "uint256",
+                "name": "Y",
+                "type": "uint256"
+              }
+            ],
+            "internalType": "struct Pairing.G1Point",
+            "name": "alfa1",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256[2]",
+                "name": "X",
+                "type": "uint256[2]"
+              },
+              {
+                "internalType": "uint256[2]",
+                "name": "Y",
+                "type": "uint256[2]"
+              }
+            ],
+            "internalType": "struct Pairing.G2Point",
+            "name": "beta2",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256[2]",
+                "name": "X",
+                "type": "uint256[2]"
+              },
+              {
+                "internalType": "uint256[2]",
+                "name": "Y",
+                "type": "uint256[2]"
+              }
+            ],
+            "internalType": "struct Pairing.G2Point",
+            "name": "gamma2",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256[2]",
+                "name": "X",
+                "type": "uint256[2]"
+              },
+              {
+                "internalType": "uint256[2]",
+                "name": "Y",
+                "type": "uint256[2]"
+              }
+            ],
+            "internalType": "struct Pairing.G2Point",
+            "name": "delta2",
+            "type": "tuple"
+          },
+          {
+            "components": [
+              {
+                "internalType": "uint256",
+                "name": "X",
+                "type": "uint256"
+              },
+              {
+                "internalType": "uint256",
+                "name": "Y",
+                "type": "uint256"
+              }
+            ],
+            "internalType": "struct Pairing.G1Point[]",
+            "name": "IC",
+            "type": "tuple[]"
+          }
+        ],
+        "internalType": "struct Verifier.VerifyingKey",
+        "name": "vk",
+        "type": "tuple"
+      }
+    ],
+    "name": "verify",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]