Repository: gakonst/ethers-rs
Pull request #1549 (2022-08-02 15:46:37)

feat: add EthError trait and derive
<!--
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
Add support for custom errors
<!--
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
* introduce new `EthError` trait, basically same as `EthCall` with derive impl
  * unify some derive code for `EthCall` and `EthError` in `calllike` 
* support human readable error parsing
* generate bindings for custom solidity errors, if there are more than 1 error in the abi, we generate an error enum `enum <name>Errors {..` see Seaport test

 
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd3c3941..383fe5ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -87,6 +87,7 @@
 
 ### Unreleased
 
+- generate error bindings for custom errors [#1549](https://github.com/gakonst/ethers-rs/pull/1549)
 - Support overloaded events
   [#1233](https://github.com/gakonst/ethers-rs/pull/1233)
 - Relax Clone requirements when Arc<Middleware> is used
diff --git a/Cargo.lock b/Cargo.lock
index c79ad4af..76d3dabd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3629,9 +3629,9 @@ dependencies = [
 
 [[package]]
 name = "solang-parser"
-version = "0.1.16"
+version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f68b8280e3f354d5646218319bcee4febe42833cef5ebf653cfc49d0a94409"
+checksum = "7fd659de36c2a8ce8cba4627e2304121acbf57d00589779a5ec3ab24569dc684"
 dependencies = [
  "itertools",
  "lalrpop",
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index df31a3ec..7ce03b73 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -1,22 +1,22 @@
 #![deny(missing_docs)]
 mod common;
+mod errors;
 mod events;
 mod methods;
 mod structs;
 mod types;
 
 use super::{util, Abigen};
-use crate::contract::structs::InternalStructs;
+use crate::{
+    contract::{methods::MethodAlias, structs::InternalStructs},
+    rawabi::JsonAbi,
+};
 use ethers_core::{
-    abi::{Abi, AbiParser},
+    abi::{Abi, AbiParser, ErrorExt, EventExt},
     macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
+    types::Bytes,
 };
 use eyre::{eyre, Context as _, Result};
-
-use crate::contract::methods::MethodAlias;
-
-use crate::rawabi::JsonAbi;
-use ethers_core::{abi::EventExt, types::Bytes};
 use proc_macro2::{Ident, Literal, TokenStream};
 use quote::quote;
 use serde::Deserialize;
@@ -34,6 +34,8 @@ pub struct ExpandedContract {
     pub contract: TokenStream,
     /// All event impls of the contract
     pub events: TokenStream,
+    /// All error impls of the contract
+    pub errors: TokenStream,
     /// All contract call struct related types
     pub call_structs: TokenStream,
     /// The contract's internal structs
@@ -43,8 +45,15 @@ pub struct ExpandedContract {
 impl ExpandedContract {
     /// Merges everything into a single module
     pub fn into_tokens(self) -> TokenStream {
-        let ExpandedContract { module, imports, contract, events, call_structs, abi_structs } =
-            self;
+        let ExpandedContract {
+            module,
+            imports,
+            contract,
+            events,
+            call_structs,
+            abi_structs,
+            errors,
+        } = self;
         quote! {
            // export all the created data types
             pub use #module::*;
@@ -53,6 +62,7 @@ impl ExpandedContract {
             pub mod #module {
                 #imports
                 #contract
+                #errors
                 #events
                 #call_structs
                 #abi_structs
@@ -87,6 +97,9 @@ pub struct Context {
     /// Manually specified method aliases.
     method_aliases: BTreeMap<String, MethodAlias>,
 
+    /// Manually specified method aliases.
+    error_aliases: BTreeMap<String, Ident>,
+
     /// Derives added to event structs and enums.
     event_derives: Vec<Path>,
 
@@ -125,6 +138,9 @@ impl Context {
         // 6. Declare the structs parsed from the human readable abi
         let abi_structs_decl = self.abi_structs()?;
 
+        // 7. declare all error types
+        let errors_decl = self.errors()?;
+
         let ethers_core = ethers_core_crate();
         let ethers_contract = ethers_contract_crate();
         let ethers_providers = ethers_providers_crate();
@@ -145,6 +161,7 @@ impl Context {
                     #contract_methods
 
                     #contract_events
+
                 }
 
                 impl<M : #ethers_providers::Middleware> From<#ethers_contract::Contract<M>> for #name<M> {
@@ -159,6 +176,7 @@ impl Context {
             imports,
             contract,
             events: events_decl,
+            errors: errors_decl,
             call_structs,
             abi_structs: abi_structs_decl,
         })
@@ -226,20 +244,30 @@ impl Context {
             event_aliases.insert(signature, alias);
         }
 
-        // also check for overloaded functions not covered by aliases, in which case we simply
+        // also check for overloaded events not covered by aliases, in which case we simply
         // numerate them
         for events in abi.events.values() {
-            let not_aliased =
-                events.iter().filter(|ev| !event_aliases.contains_key(&ev.abi_signature()));
-            if not_aliased.clone().count() > 1 {
-                let mut aliases = Vec::new();
-                // overloaded events
-                for (idx, event) in not_aliased.enumerate() {
-                    let event_name = format!("{}{}", event.name, idx + 1);
-                    aliases.push((event.abi_signature(), events::event_struct_alias(&event_name)));
-                }
-                event_aliases.extend(aliases);
-            }
+            insert_alias_names(
+                &mut event_aliases,
+                events.iter().map(|e| (e.abi_signature(), e.name.as_str())),
+                events::event_struct_alias,
+            );
+        }
+
+        let mut error_aliases = BTreeMap::new();
+        for (signature, alias) in args.error_aliases.into_iter() {
+            let alias = syn::parse_str(&alias)?;
+            error_aliases.insert(signature, alias);
+        }
+
+        // also check for overloaded errors not covered by aliases, in which case we simply
+        // numerate them
+        for errors in abi.errors.values() {
+            insert_alias_names(
+                &mut error_aliases,
+                errors.iter().map(|e| (e.abi_signature(), e.name.as_str())),
+                errors::error_struct_alias,
+            );
         }
 
         let event_derives = args
@@ -259,6 +287,7 @@ impl Context {
             contract_name: args.contract_name,
             contract_bytecode,
             method_aliases,
+            error_aliases: Default::default(),
             event_derives,
             event_aliases,
         })
@@ -290,6 +319,31 @@ impl Context {
     }
 }
 
+/// Solidity supports overloading as long as the signature of an event, error, function is unique,
+/// which results in a mapping `(name -> Vec<Element>)`
+///
+///
+/// This will populate the alias map for the value in the mapping (`Vec<Element>`) via `abi
+/// signature -> name` using the given aliases and merge it with all names not yet aliased.
+///
+/// If the iterator yields more than one element, this will simply numerate them
+fn insert_alias_names<'a, I, F>(aliases: &mut BTreeMap<String, Ident>, elements: I, get_ident: F)
+where
+    I: IntoIterator<Item = (String, &'a str)>,
+    F: Fn(&str) -> Ident,
+{
+    let not_aliased =
+        elements.into_iter().filter(|(sig, _name)| !aliases.contains_key(sig)).collect::<Vec<_>>();
+    if not_aliased.len() > 1 {
+        let mut overloaded_aliases = Vec::new();
+        for (idx, (sig, name)) in not_aliased.into_iter().enumerate() {
+            let unique_name = format!("{}{}", name, idx + 1);
+            overloaded_aliases.push((sig, get_ident(&unique_name)));
+        }
+        aliases.extend(overloaded_aliases);
+    }
+}
+
 /// Parse the abi via `Source::parse` and return if the abi defined as human readable
 fn parse_abi(abi_str: &str) -> Result<(Abi, bool, AbiParser)> {
     let mut abi_parser = AbiParser::default();
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index 0a7335c3..2235fe8d 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -1,9 +1,67 @@
 use super::{util, Context};
 
-use proc_macro2::TokenStream;
+use crate::contract::types;
+use ethers_core::{
+    abi::{Param, ParamType},
+    macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
+};
+use proc_macro2::{Ident, TokenStream};
 use quote::quote;
 
-use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
+/// Expands to the `name : type` pairs for the params
+pub(crate) fn expand_params<'a, F>(
+    params: &[Param],
+    resolve_tuple: F,
+) -> eyre::Result<Vec<(TokenStream, TokenStream)>>
+where
+    F: Fn(&str) -> Option<&'a str>,
+{
+    params
+        .iter()
+        .enumerate()
+        .map(|(idx, param)| {
+            let name = util::expand_input_name(idx, &param.name);
+            let ty = expand_param_type(param, &param.kind, |s| resolve_tuple(s))?;
+            Ok((name, ty))
+        })
+        .collect()
+}
+
+/// returns the Tokenstream for the corresponding rust type
+pub(crate) fn expand_param_type<'a, F>(
+    param: &Param,
+    kind: &ParamType,
+    resolve_tuple: F,
+) -> eyre::Result<TokenStream>
+where
+    F: Fn(&str) -> Option<&'a str>,
+{
+    match kind {
+        ParamType::Array(ty) => {
+            let ty = expand_param_type(param, ty, resolve_tuple)?;
+            Ok(quote! {
+                ::std::vec::Vec<#ty>
+            })
+        }
+        ParamType::FixedArray(ty, size) => {
+            let ty = expand_param_type(param, ty, resolve_tuple)?;
+            let size = *size;
+            Ok(quote! {[#ty; #size]})
+        }
+        ParamType::Tuple(_) => {
+            let ty = if let Some(rust_struct_name) =
+                param.internal_type.as_ref().and_then(|s| resolve_tuple(s.as_str()))
+            {
+                let ident = util::ident(rust_struct_name);
+                quote! {#ident}
+            } else {
+                types::expand(kind)?
+            };
+            Ok(ty)
+        }
+        _ => types::expand(kind),
+    }
+}
 
 pub(crate) fn imports(name: &str) -> TokenStream {
     let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
@@ -95,3 +153,38 @@ pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
         }
     }
 }
+
+/// Expands to the tuple struct definition
+pub(crate) fn expand_data_tuple(
+    name: &Ident,
+    params: &[(TokenStream, TokenStream)],
+) -> TokenStream {
+    let fields = params
+        .iter()
+        .map(|(_, ty)| {
+            quote! {
+            pub #ty }
+        })
+        .collect::<Vec<_>>();
+
+    if fields.is_empty() {
+        quote! { struct #name; }
+    } else {
+        quote! { struct #name( #( #fields ),* ); }
+    }
+}
+
+/// Expands to a struct definition with named fields
+pub(crate) fn expand_data_struct(
+    name: &Ident,
+    params: &[(TokenStream, TokenStream)],
+) -> TokenStream {
+    let fields = params
+        .iter()
+        .map(|(name, ty)| {
+            quote! { pub #name: #ty }
+        })
+        .collect::<Vec<_>>();
+
+    quote! { struct #name { #( #fields, )* } }
+}
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
new file mode 100644
index 00000000..fa3384d2
--- /dev/null
+++ b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
@@ -0,0 +1,169 @@
+//! derive error bindings
+
+use super::{util, Context};
+use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params};
+use ethers_core::{
+    abi::{ethabi::AbiError, ErrorExt},
+    macros::{ethers_contract_crate, ethers_core_crate},
+};
+use eyre::Result;
+use inflector::Inflector;
+use proc_macro2::{Ident, TokenStream};
+use quote::quote;
+
+impl Context {
+    /// Returns all error declarations
+    pub(crate) fn errors(&self) -> Result<TokenStream> {
+        let data_types = self
+            .abi
+            .errors
+            .values()
+            .flatten()
+            .map(|event| self.expand_error(event))
+            .collect::<Result<Vec<_>>>()?;
+
+        // only expand an enum when multiple errors are present
+        let errors_enum_decl = if self.abi.errors.values().flatten().count() > 1 {
+            self.expand_errors_enum()
+        } else {
+            quote! {}
+        };
+
+        Ok(quote! {
+            // HERE
+            #( #data_types )*
+
+            #errors_enum_decl
+
+            // HERE end
+        })
+    }
+
+    /// Expands an ABI error into a single error data type. This can expand either
+    /// into a structure or a tuple in the case where all error parameters are anonymous.
+    fn expand_error(&self, error: &AbiError) -> Result<TokenStream> {
+        let sig = self.error_aliases.get(&error.abi_signature()).cloned();
+        let abi_signature = error.abi_signature();
+
+        let error_name = error_struct_name(&error.name, sig);
+
+        let fields = self.expand_error_params(error)?;
+
+        // expand as a tuple if all fields are anonymous
+        let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty());
+        let data_type_definition = if all_anonymous_fields {
+            // expand to a tuple struct
+            expand_data_tuple(&error_name, &fields)
+        } else {
+            // expand to a struct
+            expand_data_struct(&error_name, &fields)
+        };
+
+        let doc = format!(
+            "Custom Error type `{}` with signature `{}` and selector `{:?}`",
+            error.name,
+            abi_signature,
+            error.selector()
+        );
+        let abi_signature_doc = util::expand_doc(&doc);
+        let ethers_contract = ethers_contract_crate();
+        // use the same derives as for events
+        let derives = util::expand_derives(&self.event_derives);
+
+        let error_name = &error.name;
+
+        Ok(quote! {
+             #abi_signature_doc
+            #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)]
+            #[etherror( name = #error_name, abi = #abi_signature )]
+            pub #data_type_definition
+        })
+    }
+
+    /// Expands to the `name : type` pairs of the function's outputs
+    fn expand_error_params(&self, error: &AbiError) -> Result<Vec<(TokenStream, TokenStream)>> {
+        expand_params(&error.inputs, |s| self.internal_structs.get_struct_type(s))
+    }
+
+    /// The name ident of the errors enum
+    fn expand_error_enum_name(&self) -> Ident {
+        util::ident(&format!("{}Errors", self.contract_ident))
+    }
+
+    /// Generate an enum with a variant for each event
+    fn expand_errors_enum(&self) -> TokenStream {
+        let variants = self
+            .abi
+            .errors
+            .values()
+            .flatten()
+            .map(|err| {
+                error_struct_name(&err.name, self.error_aliases.get(&err.abi_signature()).cloned())
+            })
+            .collect::<Vec<_>>();
+
+        let ethers_core = ethers_core_crate();
+        let ethers_contract = ethers_contract_crate();
+
+        // use the same derives as for events
+        let derives = util::expand_derives(&self.event_derives);
+        let enum_name = self.expand_error_enum_name();
+
+        quote! {
+           #[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)]
+            pub enum #enum_name {
+                #(#variants(#variants)),*
+            }
+
+        impl  #ethers_core::abi::AbiDecode for #enum_name {
+            fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
+                 #(
+                    if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) {
+                        return Ok(#enum_name::#variants(decoded))
+                    }
+                )*
+                Err(#ethers_core::abi::Error::InvalidData.into())
+            }
+        }
+
+         impl  #ethers_core::abi::AbiEncode for #enum_name {
+            fn encode(self) -> Vec<u8> {
+                match self {
+                    #(
+                        #enum_name::#variants(element) => element.encode()
+                    ),*
+                }
+            }
+        }
+
+        impl ::std::fmt::Display for #enum_name {
+            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+                match self {
+                    #(
+                        #enum_name::#variants(element) => element.fmt(f)
+                    ),*
+                }
+            }
+        }
+
+        #(
+            impl ::std::convert::From<#variants> for #enum_name {
+                fn from(var: #variants) -> Self {
+                    #enum_name::#variants(var)
+                }
+            }
+        )*
+
+        }
+    }
+}
+
+/// Expands an ABI error into an identifier for its event data type.
+fn error_struct_name(error_name: &str, alias: Option<Ident>) -> Ident {
+    alias.unwrap_or_else(|| util::ident(error_name))
+}
+
+/// Returns the alias name for an error
+pub(crate) fn error_struct_alias(name: &str) -> Ident {
+    util::ident(&name.to_pascal_case())
+}
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 11b1641a..314d03df 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -1,18 +1,19 @@
 use std::collections::{btree_map::Entry, BTreeMap, HashMap};
 
-use eyre::{Context as _, Result};
-use inflector::Inflector;
-use proc_macro2::{Literal, TokenStream};
-use quote::quote;
-use syn::Ident;
-
+use super::{types, util, Context};
+use crate::contract::common::{
+    expand_data_struct, expand_data_tuple, expand_param_type, expand_params,
+};
 use ethers_core::{
     abi::{Function, FunctionExt, Param, ParamType},
     macros::{ethers_contract_crate, ethers_core_crate},
     types::Selector,
 };
-
-use super::{types, util, Context};
+use eyre::{Context as _, Result};
+use inflector::Inflector;
+use proc_macro2::{Literal, TokenStream};
+use quote::quote;
+use syn::Ident;
 
 /// The maximum amount of overloaded functions that are attempted to auto aliased with their param
 /// name. If there is a function that with `NAME_ALIASING_OVERLOADED_FUNCTIONS_CAP` overloads then
@@ -142,7 +143,7 @@ impl Context {
     }
 
     /// Expands to the corresponding struct type based on the inputs of the given function
-    fn expand_return_struct(
+    pub fn expand_return_struct(
         &self,
         function: &Function,
         alias: Option<&MethodAlias>,
@@ -296,15 +297,9 @@ impl Context {
 
     /// Expands to the `name : type` pairs of the function's outputs
     fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
-        fun.outputs
-            .iter()
-            .enumerate()
-            .map(|(idx, param)| {
-                let name = util::expand_input_name(idx, &param.name);
-                let ty = self.expand_output_param_type(fun, param, &param.kind)?;
-                Ok((name, ty))
-            })
-            .collect()
+        expand_params(&fun.outputs, |s| {
+            self.internal_structs.get_function_output_struct_type(&fun.name, s)
+        })
     }
 
     /// Expands to the return type of a function
@@ -388,39 +383,16 @@ impl Context {
         }
     }
 
-    /// returns the Tokenstream for the corresponding rust type of the output param
+    /// returns the TokenStream for the corresponding rust type of the output param
     fn expand_output_param_type(
         &self,
         fun: &Function,
         param: &Param,
         kind: &ParamType,
     ) -> Result<TokenStream> {
-        match kind {
-            ParamType::Array(ty) => {
-                let ty = self.expand_output_param_type(fun, param, ty)?;
-                Ok(quote! {
-                    ::std::vec::Vec<#ty>
-                })
-            }
-            ParamType::FixedArray(ty, size) => {
-                let ty = self.expand_output_param_type(fun, param, ty)?;
-                let size = *size;
-                Ok(quote! {[#ty; #size]})
-            }
-            ParamType::Tuple(_) => {
-                let ty = if let Some(rust_struct_name) =
-                    param.internal_type.as_ref().and_then(|s| {
-                        self.internal_structs.get_function_output_struct_type(&fun.name, s)
-                    }) {
-                    let ident = util::ident(rust_struct_name);
-                    quote! {#ident}
-                } else {
-                    types::expand(kind)?
-                };
-                Ok(ty)
-            }
-            _ => types::expand(kind),
-        }
+        expand_param_type(param, kind, |s| {
+            self.internal_structs.get_function_output_struct_type(&fun.name, s)
+        })
     }
 
     /// Expands a single function with the given alias
@@ -717,35 +689,6 @@ fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAli
     }
 }
 
-/// Expands to the tuple struct definition
-fn expand_data_tuple(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream {
-    let fields = params
-        .iter()
-        .map(|(_, ty)| {
-            quote! {
-            pub #ty }
-        })
-        .collect::<Vec<_>>();
-
-    if fields.is_empty() {
-        quote! { struct #name; }
-    } else {
-        quote! { struct #name( #( #fields ),* ); }
-    }
-}
-
-/// Expands to the struct definition of a call struct
-fn expand_data_struct(name: &Ident, params: &[(TokenStream, TokenStream)]) -> TokenStream {
-    let fields = params
-        .iter()
-        .map(|(name, ty)| {
-            quote! { pub #name: #ty }
-        })
-        .collect::<Vec<_>>();
-
-    quote! { struct #name { #( #fields, )* } }
-}
-
 #[cfg(test)]
 mod tests {
     use ethers_core::abi::ParamType;
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index 947604b9..fc82f34f 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -334,6 +334,11 @@ impl InternalStructs {
             .map(String::as_str)
     }
 
+    /// Returns the name of the rust type for the type
+    pub fn get_struct_type(&self, internal_type: &str) -> Option<&str> {
+        self.rust_type_names.get(struct_type_identifier(internal_type)).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
diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs
index a538df71..b9482b52 100644
--- a/ethers-contract/ethers-contract-abigen/src/lib.rs
+++ b/ethers-contract/ethers-contract-abigen/src/lib.rs
@@ -72,6 +72,9 @@ pub struct Abigen {
 
     /// Manually specified event name aliases.
     event_aliases: HashMap<String, String>,
+
+    /// Manually specified error name aliases.
+    error_aliases: HashMap<String, String>,
 }
 
 impl Abigen {
@@ -85,6 +88,7 @@ impl Abigen {
             event_derives: Vec::new(),
             event_aliases: HashMap::new(),
             rustfmt: true,
+            error_aliases: Default::default(),
         })
     }
 
@@ -126,6 +130,17 @@ impl Abigen {
         self
     }
 
+    /// Manually adds a solidity error alias to specify what the error struct will be in Rust.
+    #[must_use]
+    pub fn add_error_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
+    where
+        S1: Into<String>,
+        S2: Into<String>,
+    {
+        self.error_aliases.insert(signature.into(), alias.into());
+        self
+    }
+
     /// Specify whether or not to format the code using a locally installed copy
     /// of `rustfmt`.
     ///
diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs
index f86163ed..cf8c244e 100644
--- a/ethers-contract/ethers-contract-derive/src/call.rs
+++ b/ethers-contract/ethers-contract-derive/src/call.rs
@@ -1,17 +1,17 @@
 //! Helper functions for deriving `EthCall`
 
-use crate::{abi_ty, utils};
+use crate::{calllike::*, utils, utils::ident};
 use ethers_core::{
-    abi::{Function, FunctionExt, HumanReadableParser},
+    abi::{FunctionExt, HumanReadableParser},
     macros::{ethers_contract_crate, ethers_core_crate},
 };
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::TokenStream;
 use quote::quote;
-use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
+use syn::{parse::Error, DeriveInput};
 
 /// Generates the `ethcall` trait support
 pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
-    let attributes = match parse_call_attributes(&input) {
+    let attributes = match parse_calllike_attributes(&input, "ethcall") {
         Ok(attributes) => attributes,
         Err(errors) => return errors,
     };
@@ -48,7 +48,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
     function.name = function_call_name.clone();
     let abi = function.abi_signature();
     let selector = utils::selector(function.selector());
-    let decode_impl = derive_decode_impl_from_function(&function);
+    let decode_impl = derive_decode_impl_from_params(&function.inputs, ident("EthCall"));
 
     derive_trait_impls(
         &input,
@@ -59,6 +59,25 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
     )
 }
 
+/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
+fn derive_trait_impls_with_abi_type(
+    input: &DeriveInput,
+    function_call_name: &str,
+    abi_signature: Option<&str>,
+) -> Result<TokenStream, Error> {
+    let abi_signature = if let Some(abi) = abi_signature {
+        quote! {#abi}
+    } else {
+        utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?
+    };
+
+    let abi_signature = quote! {
+         #abi_signature.into()
+    };
+    let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthCall"))?;
+    Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
+}
+
 /// Generates the EthCall implementation
 pub fn derive_trait_impls(
     input: &DeriveInput,
@@ -93,167 +112,11 @@ pub fn derive_trait_impls(
                 #abi_signature
             }
         }
-
-        impl  #core_crate::abi::AbiDecode for #struct_name {
-            fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
-                #decode_impl
-            }
-        }
-
-        impl #core_crate::abi::AbiEncode for #struct_name {
-            fn encode(self) -> ::std::vec::Vec<u8> {
-                let tokens =  #core_crate::abi::Tokenize::into_tokens(self);
-                let selector = <Self as #contract_crate::EthCall>::selector();
-                let encoded = #core_crate::abi::encode(&tokens);
-                selector
-                    .iter()
-                    .copied()
-                    .chain(encoded.into_iter())
-                    .collect()
-            }
-        }
-
     };
-    let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
+    let codec_impl = derive_codec_impls(input, decode_impl, ident("EthCall"));
 
     quote! {
-        #tokenize_impl
         #ethcall_impl
+        #codec_impl
     }
 }
-
-/// Generates the decode implementation based on the function's input types
-fn derive_decode_impl_from_function(function: &Function) -> TokenStream {
-    let datatypes = function.inputs.iter().map(|input| utils::param_type_quote(&input.kind));
-    let datatypes_array = quote! {[#( #datatypes ),*]};
-    derive_decode_impl(datatypes_array)
-}
-
-/// Generates the decode implementation based on the function's runtime `AbiType` impl
-fn derive_decode_impl_with_abi_type(input: &DeriveInput) -> Result<TokenStream, Error> {
-    let datatypes_array = utils::derive_abi_parameters_array(input, "EthCall")?;
-    Ok(derive_decode_impl(datatypes_array))
-}
-
-fn derive_decode_impl(datatypes_array: TokenStream) -> TokenStream {
-    let core_crate = ethers_core_crate();
-    let contract_crate = ethers_contract_crate();
-    let data_types_init = quote! {let data_types = #datatypes_array;};
-
-    quote! {
-        let bytes = bytes.as_ref();
-        if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::EthCall>::selector() {
-            return Err(#contract_crate::AbiError::WrongSelector);
-        }
-        #data_types_init
-        let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?;
-        Ok(<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens))?)
-    }
-}
-
-/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
-fn derive_trait_impls_with_abi_type(
-    input: &DeriveInput,
-    function_call_name: &str,
-    abi_signature: Option<&str>,
-) -> Result<TokenStream, Error> {
-    let abi_signature = if let Some(abi) = abi_signature {
-        quote! {#abi}
-    } else {
-        utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthCall")?
-    };
-
-    let abi_signature = quote! {
-         #abi_signature.into()
-    };
-    let decode_impl = derive_decode_impl_with_abi_type(input)?;
-    Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
-}
-
-/// All the attributes the `EthCall` macro supports
-#[derive(Default)]
-struct EthCallAttributes {
-    name: Option<(String, Span)>,
-    abi: Option<(String, Span)>,
-}
-
-/// extracts the attributes from the struct annotated with `EthCall`
-fn parse_call_attributes(input: &DeriveInput) -> Result<EthCallAttributes, TokenStream> {
-    let mut result = EthCallAttributes::default();
-    for a in input.attrs.iter() {
-        if let AttrStyle::Outer = a.style {
-            if let Ok(Meta::List(meta)) = a.parse_meta() {
-                if meta.path.is_ident("ethcall") {
-                    for n in meta.nested.iter() {
-                        if let NestedMeta::Meta(meta) = n {
-                            match meta {
-                                Meta::Path(path) => {
-                                    return Err(Error::new(
-                                        path.span(),
-                                        "unrecognized ethcall parameter",
-                                    )
-                                    .to_compile_error())
-                                }
-                                Meta::List(meta) => {
-                                    return Err(Error::new(
-                                        meta.path.span(),
-                                        "unrecognized ethcall parameter",
-                                    )
-                                    .to_compile_error())
-                                }
-                                Meta::NameValue(meta) => {
-                                    if meta.path.is_ident("name") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.name.is_none() {
-                                                result.name =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "name already specified",
-                                                )
-                                                .to_compile_error())
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "name must be a string",
-                                            )
-                                            .to_compile_error())
-                                        }
-                                    } else if meta.path.is_ident("abi") {
-                                        if let Lit::Str(ref lit_str) = meta.lit {
-                                            if result.abi.is_none() {
-                                                result.abi =
-                                                    Some((lit_str.value(), lit_str.span()));
-                                            } else {
-                                                return Err(Error::new(
-                                                    meta.span(),
-                                                    "abi already specified",
-                                                )
-                                                .to_compile_error())
-                                            }
-                                        } else {
-                                            return Err(Error::new(
-                                                meta.span(),
-                                                "abi must be a string",
-                                            )
-                                            .to_compile_error())
-                                        }
-                                    } else {
-                                        return Err(Error::new(
-                                            meta.span(),
-                                            "unrecognized ethcall parameter",
-                                        )
-                                        .to_compile_error())
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-    Ok(result)
-}
diff --git a/ethers-contract/ethers-contract-derive/src/calllike.rs b/ethers-contract/ethers-contract-derive/src/calllike.rs
new file mode 100644
index 00000000..21743758
--- /dev/null
+++ b/ethers-contract/ethers-contract-derive/src/calllike.rs
@@ -0,0 +1,174 @@
+//! Code used by both `call` and `error`
+
+use crate::{abi_ty, utils};
+use ethers_core::{
+    abi::Param,
+    macros::{ethers_contract_crate, ethers_core_crate},
+};
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{parse::Error, spanned::Spanned as _, AttrStyle, DeriveInput, Lit, Meta, NestedMeta};
+
+/// All the attributes the `EthCall`/`EthError` macro supports
+#[derive(Default)]
+pub struct EthCalllikeAttributes {
+    pub name: Option<(String, Span)>,
+    pub abi: Option<(String, Span)>,
+}
+
+/// extracts the attributes from the struct annotated with the given attribute
+pub fn parse_calllike_attributes(
+    input: &DeriveInput,
+    attr_name: &str,
+) -> Result<EthCalllikeAttributes, TokenStream> {
+    let mut result = EthCalllikeAttributes::default();
+    for a in input.attrs.iter() {
+        if let AttrStyle::Outer = a.style {
+            if let Ok(Meta::List(meta)) = a.parse_meta() {
+                if meta.path.is_ident(attr_name) {
+                    for n in meta.nested.iter() {
+                        if let NestedMeta::Meta(meta) = n {
+                            match meta {
+                                Meta::Path(path) => {
+                                    return Err(Error::new(
+                                        path.span(),
+                                        format!("unrecognized {} parameter", attr_name),
+                                    )
+                                    .to_compile_error())
+                                }
+                                Meta::List(meta) => {
+                                    return Err(Error::new(
+                                        meta.path.span(),
+                                        format!("unrecognized {} parameter", attr_name),
+                                    )
+                                    .to_compile_error())
+                                }
+                                Meta::NameValue(meta) => {
+                                    if meta.path.is_ident("name") {
+                                        if let Lit::Str(ref lit_str) = meta.lit {
+                                            if result.name.is_none() {
+                                                result.name =
+                                                    Some((lit_str.value(), lit_str.span()));
+                                            } else {
+                                                return Err(Error::new(
+                                                    meta.span(),
+                                                    "name already specified",
+                                                )
+                                                .to_compile_error())
+                                            }
+                                        } else {
+                                            return Err(Error::new(
+                                                meta.span(),
+                                                "name must be a string",
+                                            )
+                                            .to_compile_error())
+                                        }
+                                    } else if meta.path.is_ident("abi") {
+                                        if let Lit::Str(ref lit_str) = meta.lit {
+                                            if result.abi.is_none() {
+                                                result.abi =
+                                                    Some((lit_str.value(), lit_str.span()));
+                                            } else {
+                                                return Err(Error::new(
+                                                    meta.span(),
+                                                    "abi already specified",
+                                                )
+                                                .to_compile_error())
+                                            }
+                                        } else {
+                                            return Err(Error::new(
+                                                meta.span(),
+                                                "abi must be a string",
+                                            )
+                                            .to_compile_error())
+                                        }
+                                    } else {
+                                        return Err(Error::new(
+                                            meta.span(),
+                                            format!("unrecognized {} parameter", attr_name),
+                                        )
+                                        .to_compile_error())
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    Ok(result)
+}
+
+/// Generates the decode implementation based on the type's runtime `AbiType` impl
+pub fn derive_decode_impl_with_abi_type(
+    input: &DeriveInput,
+    trait_ident: Ident,
+) -> Result<TokenStream, Error> {
+    let datatypes_array = utils::derive_abi_parameters_array(input, &trait_ident.to_string())?;
+    Ok(derive_decode_impl(datatypes_array, trait_ident))
+}
+
+/// Generates the decode implementation based on the params
+pub fn derive_decode_impl_from_params(params: &[Param], trait_ident: Ident) -> TokenStream {
+    let datatypes = params.iter().map(|input| utils::param_type_quote(&input.kind));
+    let datatypes_array = quote! {[#( #datatypes ),*]};
+    derive_decode_impl(datatypes_array, trait_ident)
+}
+
+pub fn derive_decode_impl(datatypes_array: TokenStream, trait_ident: Ident) -> TokenStream {
+    let core_crate = ethers_core_crate();
+    let contract_crate = ethers_contract_crate();
+    let data_types_init = quote! {let data_types = #datatypes_array;};
+
+    quote! {
+        let bytes = bytes.as_ref();
+        if bytes.len() < 4 || bytes[..4] != <Self as #contract_crate::#trait_ident>::selector() {
+            return Err(#contract_crate::AbiError::WrongSelector);
+        }
+        #data_types_init
+        let data_tokens = #core_crate::abi::decode(&data_types, &bytes[4..])?;
+        Ok(<Self as #core_crate::abi::Tokenizable>::from_token( #core_crate::abi::Token::Tuple(data_tokens))?)
+    }
+}
+
+/// Generates the Codec implementation
+pub fn derive_codec_impls(
+    input: &DeriveInput,
+    decode_impl: TokenStream,
+    trait_ident: Ident,
+) -> TokenStream {
+    // the ethers crates to use
+    let core_crate = ethers_core_crate();
+    let contract_crate = ethers_contract_crate();
+    let struct_name = &input.ident;
+
+    let codec_impl = quote! {
+
+        impl #core_crate::abi::AbiDecode for #struct_name {
+            fn decode(bytes: impl AsRef<[u8]>) -> ::std::result::Result<Self, #core_crate::abi::AbiError> {
+                #decode_impl
+            }
+        }
+
+        impl #core_crate::abi::AbiEncode for #struct_name {
+            fn encode(self) -> ::std::vec::Vec<u8> {
+                let tokens =  #core_crate::abi::Tokenize::into_tokens(self);
+                let selector = <Self as #contract_crate::#trait_ident>::selector();
+                let encoded = #core_crate::abi::encode(&tokens);
+                selector
+                    .iter()
+                    .copied()
+                    .chain(encoded.into_iter())
+                    .collect()
+            }
+        }
+
+    };
+    let tokenize_impl = abi_ty::derive_tokenizeable_impl(input);
+
+    quote! {
+        #tokenize_impl
+        #codec_impl
+    }
+}
diff --git a/ethers-contract/ethers-contract-derive/src/error.rs b/ethers-contract/ethers-contract-derive/src/error.rs
new file mode 100644
index 00000000..c0914dc1
--- /dev/null
+++ b/ethers-contract/ethers-contract-derive/src/error.rs
@@ -0,0 +1,116 @@
+//! Helper functions for deriving `EthError`
+
+use crate::{calllike::*, utils, utils::ident};
+use ethers_core::{
+    abi::{ErrorExt, HumanReadableParser},
+    macros::{ethers_contract_crate, ethers_core_crate},
+};
+use proc_macro2::TokenStream;
+use quote::quote;
+use syn::{parse::Error, DeriveInput};
+
+/// Generates the `EthError` trait support
+pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> TokenStream {
+    let attributes = match parse_calllike_attributes(&input, "etherror") {
+        Ok(attributes) => attributes,
+        Err(errors) => return errors,
+    };
+
+    let error_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
+
+    let mut error = if let Some((src, span)) = attributes.abi {
+        let raw_function_sig = src.trim_start_matches("error ").trim_start();
+        // try to parse as solidity error
+        if let Ok(fun) = HumanReadableParser::parse_error(&src) {
+            fun
+        } else {
+            // try to determine the abi by using its fields at runtime
+            return match derive_trait_impls_with_abi_type(
+                &input,
+                &error_name,
+                Some(raw_function_sig),
+            ) {
+                Ok(derived) => derived,
+                Err(err) => {
+                    Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err))
+                        .to_compile_error()
+                }
+            }
+        }
+    } else {
+        // try to determine the abi by using its fields at runtime
+        return match derive_trait_impls_with_abi_type(&input, &error_name, None) {
+            Ok(derived) => derived,
+            Err(err) => err.to_compile_error(),
+        }
+    };
+    error.name = error_name.clone();
+    let abi = error.abi_signature();
+    let selector = utils::selector(error.selector());
+    let decode_impl = derive_decode_impl_from_params(&error.inputs, ident("EthError"));
+
+    derive_trait_impls(&input, &error_name, quote! {#abi.into()}, Some(selector), decode_impl)
+}
+
+/// Use the `AbiType` trait to determine the correct `ParamType` and signature at runtime
+fn derive_trait_impls_with_abi_type(
+    input: &DeriveInput,
+    function_call_name: &str,
+    abi_signature: Option<&str>,
+) -> Result<TokenStream, Error> {
+    let abi_signature = if let Some(abi) = abi_signature {
+        quote! {#abi}
+    } else {
+        utils::derive_abi_signature_with_abi_type(input, function_call_name, "EthError")?
+    };
+
+    let abi_signature = quote! {
+         #abi_signature.into()
+    };
+    let decode_impl = derive_decode_impl_with_abi_type(input, ident("EthError"))?;
+    Ok(derive_trait_impls(input, function_call_name, abi_signature, None, decode_impl))
+}
+
+/// Generates the EthError implementation
+pub fn derive_trait_impls(
+    input: &DeriveInput,
+    function_call_name: &str,
+    abi_signature: TokenStream,
+    selector: Option<TokenStream>,
+    decode_impl: TokenStream,
+) -> TokenStream {
+    // the ethers crates to use
+    let core_crate = ethers_core_crate();
+    let contract_crate = ethers_contract_crate();
+    let struct_name = &input.ident;
+
+    let selector = selector.unwrap_or_else(|| {
+        quote! {
+             #core_crate::utils::id(Self::abi_signature())
+        }
+    });
+
+    let etherror_impl = quote! {
+        impl #contract_crate::EthError for #struct_name {
+
+            fn error_name() -> ::std::borrow::Cow<'static, str> {
+                #function_call_name.into()
+            }
+
+            fn selector() -> #core_crate::types::Selector {
+                #selector
+            }
+
+            fn abi_signature() -> ::std::borrow::Cow<'static, str> {
+                #abi_signature
+            }
+        }
+
+    };
+    let codec_impl = derive_codec_impls(input, decode_impl, ident("EthError"));
+
+    quote! {
+        #etherror_impl
+        #codec_impl
+    }
+}
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index b505d19e..81f3c773 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -11,8 +11,10 @@ use abigen::Contracts;
 pub(crate) mod abi_ty;
 mod abigen;
 mod call;
+pub(crate) mod calllike;
 mod codec;
 mod display;
+mod error;
 mod event;
 mod spanned;
 pub(crate) mod utils;
@@ -281,3 +283,40 @@ pub fn derive_abi_call(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     TokenStream::from(call::derive_eth_call_impl(input))
 }
+
+/// Derives the `EthError` and `Tokenizeable` trait for the labeled type.
+///
+/// Additional arguments can be specified using the `#[etherror(...)]`
+/// attribute:
+///
+/// For the struct:
+///
+/// - `name`, `name = "..."`: Overrides the generated `EthCall` function name, default is the
+///  struct's name.
+/// - `abi`, `abi = "..."`: The ABI signature for the function this call's data corresponds to.
+///
+///  NOTE: in order to successfully parse the `abi` (`<name>(<args>,...)`) the `<name`>
+///   must match either the struct name or the name attribute: `#[ethcall(name ="<name>"]`
+///
+/// # Example
+///
+/// ```ignore
+/// use ethers_contract::EthError;
+///
+/// #[derive(Debug, Clone, EthError)]
+/// #[etherror(name ="my_error")]
+/// struct MyError {
+///     addr: Address,
+///     old_value: String,
+///     new_value: String,
+/// }
+/// assert_eq!(
+///     MyCall::abi_signature().as_ref(),
+///     "my_error(address,string,string)"
+/// );
+/// ```
+#[proc_macro_derive(EthError, attributes(etherror))]
+pub fn derive_abi_error(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    TokenStream::from(error::derive_eth_error_impl(input))
+}
diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs
index 65319f06..b8691552 100644
--- a/ethers-contract/ethers-contract-derive/src/utils.rs
+++ b/ethers-contract/ethers-contract-derive/src/utils.rs
@@ -1,11 +1,15 @@
 use ethers_core::{abi::ParamType, macros::ethers_core_crate, types::Selector};
-use proc_macro2::Literal;
+use proc_macro2::{Ident, Literal, Span};
 use quote::{quote, quote_spanned};
 use syn::{
     parse::Error, spanned::Spanned as _, Data, DeriveInput, Expr, Fields, GenericArgument, Lit,
     PathArguments, Type,
 };
 
+pub fn ident(name: &str) -> Ident {
+    Ident::new(name, Span::call_site())
+}
+
 pub fn signature(hash: &[u8]) -> proc_macro2::TokenStream {
     let core_crate = ethers_core_crate();
     let bytes = hash.iter().copied().map(Literal::u8_unsuffixed);
diff --git a/ethers-contract/src/error.rs b/ethers-contract/src/error.rs
new file mode 100644
index 00000000..cfac0651
--- /dev/null
+++ b/ethers-contract/src/error.rs
@@ -0,0 +1,20 @@
+use ethers_core::{
+    abi::{AbiDecode, AbiEncode, Tokenizable},
+    types::Selector,
+    utils::id,
+};
+use std::borrow::Cow;
+
+/// A helper trait for types that represents a custom error type
+pub trait EthError: Tokenizable + AbiDecode + AbiEncode + Send + Sync {
+    /// The name of the error
+    fn error_name() -> Cow<'static, str>;
+
+    /// Retrieves the ABI signature for the error
+    fn abi_signature() -> Cow<'static, str>;
+
+    /// The selector of the error
+    fn selector() -> Selector {
+        id(Self::abi_signature())
+    }
+}
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 25bcf8d5..6b8ba377 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -11,6 +11,9 @@ pub use base::{decode_function_data, encode_function_data, AbiError, BaseContrac
 mod call;
 pub use call::{ContractError, EthCall};
 
+mod error;
+pub use error::EthError;
+
 mod factory;
 pub use factory::{ContractDeployer, ContractFactory};
 
@@ -42,7 +45,9 @@ pub use ethers_contract_abigen::{Abigen, MultiAbigen};
 
 #[cfg(any(test, feature = "abigen"))]
 #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
-pub use ethers_contract_derive::{abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent};
+pub use ethers_contract_derive::{
+    abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent,
+};
 
 // Hide the Lazy re-export, it's just for convenience
 #[doc(hidden)]
diff --git a/ethers-core/src/abi/human_readable/lexer.rs b/ethers-core/src/abi/human_readable/lexer.rs
index 3c9c6f24..4c7fea58 100644
--- a/ethers-core/src/abi/human_readable/lexer.rs
+++ b/ethers-core/src/abi/human_readable/lexer.rs
@@ -1,4 +1,6 @@
-use ethabi::{Constructor, Event, EventParam, Function, Param, ParamType, StateMutability};
+use ethabi::{
+    AbiError, Constructor, Event, EventParam, Function, Param, ParamType, StateMutability,
+};
 use std::{fmt, iter::Peekable, str::CharIndices};
 use unicode_xid::UnicodeXID;
 
@@ -320,6 +322,18 @@ impl<'input> HumanReadableParser<'input> {
         Self::new(input).take_function()
     }
 
+    /// Parses a [Function] from a human readable form
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use ethers_core::abi::HumanReadableParser;
+    /// let err = HumanReadableParser::parse_error("error MyError(address author, string oldValue, string newValue)").unwrap();
+    /// ```
+    pub fn parse_error(input: &'input str) -> Result<AbiError, LexerError> {
+        Self::new(input).take_error()
+    }
+
     /// Parses a [Constructor] from a human readable form
     ///
     /// # Example
@@ -344,6 +358,15 @@ impl<'input> HumanReadableParser<'input> {
         Self::new(input).take_event()
     }
 
+    /// Returns the next `Error` and consumes the underlying tokens
+    pub fn take_error(&mut self) -> Result<AbiError, LexerError> {
+        let name = self.take_identifier(Token::Error)?;
+        self.take_open_parenthesis()?;
+        let inputs = self.take_function_params()?;
+        self.take_close_parenthesis()?;
+        Ok(AbiError { name: name.to_string(), inputs })
+    }
+
     /// Returns the next `Constructor` and consumes the underlying tokens
     pub fn take_constructor(&mut self) -> Result<Constructor, LexerError> {
         self.take_next_exact(Token::Constructor)?;
@@ -352,6 +375,7 @@ impl<'input> HumanReadableParser<'input> {
         self.take_close_parenthesis()?;
         Ok(Constructor { inputs })
     }
+
     /// Returns the next `Function` and consumes the underlying tokens
     pub fn take_function(&mut self) -> Result<Function, LexerError> {
         let name = self.take_identifier(Token::Function)?;
@@ -833,6 +857,31 @@ pub enum DataLocation {
 mod tests {
     use super::*;
 
+    #[test]
+    fn parse_error() {
+        let f = AbiError {
+            name: "MyError".to_string(),
+            inputs: vec![
+                Param { name: "author".to_string(), kind: ParamType::Address, internal_type: None },
+                Param {
+                    name: "oldValue".to_string(),
+                    kind: ParamType::String,
+                    internal_type: None,
+                },
+                Param {
+                    name: "newValue".to_string(),
+                    kind: ParamType::String,
+                    internal_type: None,
+                },
+            ],
+        };
+        let parsed = HumanReadableParser::parse_error(
+            "error MyError(address author, string oldValue, string newValue)",
+        )
+        .unwrap();
+        assert_eq!(f, parsed);
+    }
+
     #[test]
     fn parse_constructor() {
         let f = Constructor {
diff --git a/ethers-core/src/abi/human_readable/mod.rs b/ethers-core/src/abi/human_readable/mod.rs
index 763798d7..ca832e8f 100644
--- a/ethers-core/src/abi/human_readable/mod.rs
+++ b/ethers-core/src/abi/human_readable/mod.rs
@@ -1,3 +1,4 @@
+use ethabi::AbiError;
 use std::collections::{BTreeMap, HashMap, VecDeque};
 
 use crate::abi::{
@@ -82,6 +83,17 @@ impl AbiParser {
             if line.starts_with("event") {
                 let event = self.parse_event(line)?;
                 abi.events.entry(event.name.clone()).or_default().push(event);
+            } else if let Some(err) = line.strip_prefix("error") {
+                // an error is essentially a function without outputs, so we parse as function here
+                let function = match self.parse_function(err) {
+                    Ok(function) => function,
+                    Err(_) => bail!("Illegal abi `{}`, expected error", line),
+                };
+                if !function.outputs.is_empty() {
+                    bail!("Illegal abi `{}`, expected error", line);
+                }
+                let error = AbiError { name: function.name, inputs: function.inputs };
+                abi.errors.entry(error.name.clone()).or_default().push(error);
             } else if line.starts_with("constructor") {
                 let inputs = self
                     .constructor_inputs(line)?
@@ -103,7 +115,7 @@ impl AbiParser {
                 // function may have shorthand declaration, so it won't start with "function"
                 let function = match self.parse_function(line) {
                     Ok(function) => function,
-                    Err(_) => bail!("Illegal abi `{}`", line),
+                    Err(_) => bail!("Illegal abi `{}`, expected function", line),
                 };
                 abi.functions.entry(function.name.clone()).or_default().push(function);
             }
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index 88c57f88..fdb6eb08 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -70,6 +70,29 @@ impl EventExt for Event {
     }
 }
 
+/// Extension trait for `ethabi::AbiError`.
+pub trait ErrorExt {
+    /// Compute the method signature in the standard ABI format.
+    fn abi_signature(&self) -> String;
+
+    /// Compute the Keccak256 error selector used by contract ABIs.
+    fn selector(&self) -> Selector;
+}
+
+impl ErrorExt for ethabi::AbiError {
+    fn abi_signature(&self) -> String {
+        if self.inputs.is_empty() {
+            return format!("{}()", self.name)
+        }
+        let inputs = self.inputs.iter().map(|p| p.kind.to_string()).collect::<Vec<_>>().join(",");
+        format!("{}({})", self.name, inputs)
+    }
+
+    fn selector(&self) -> Selector {
+        id(self.abi_signature())
+    }
+}
+
 /// A trait for types that can be represented in the ethereum ABI.
 pub trait AbiType {
     /// The native ABI type this type represents.
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 60db42f8..2d9f794e 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -35,7 +35,7 @@ tempfile = { version = "3.3.0", optional = true }
 fs_extra = { version = "1.2.0", optional = true }
 sha2 = { version = "0.10.2", default-features = false, optional = true }
 dunce = "1.0.2"
-solang-parser = { default-features = false, version = "=0.1.16" }
+solang-parser = { default-features = false, version = "=0.1.17" }
 rayon = "1.5.3"
 rand = { version = "0.8.5", optional = true }
 path-slash = "0.2.0"


Test patch:
diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs
index 9ca7e9a9..5dd25efc 100644
--- a/ethers-contract/tests/it/abigen.rs
+++ b/ethers-contract/tests/it/abigen.rs
@@ -629,6 +629,18 @@ fn can_gen_seaport() {
         "fulfillAdvancedOrder(((address,address,(uint8,address,uint256,uint256,uint256)[],(uint8,address,uint256,uint256,uint256,address)[],uint8,uint256,uint256,bytes32,uint256,bytes32,uint256),uint120,uint120,bytes,bytes),(uint256,uint8,uint256,uint256,bytes32[])[],bytes32,address)"
     );
     assert_eq!(hex::encode(FulfillAdvancedOrderCall::selector()), "e7acab24");
+
+    assert_codec::<SeaportErrors>();
+    let err = SeaportErrors::BadContractSignature(BadContractSignature::default());
+
+    let encoded = err.clone().encode();
+    assert_eq!(err, SeaportErrors::decode(encoded).unwrap());
+
+    let err = SeaportErrors::ConsiderationNotMet(ConsiderationNotMet {
+        order_index: U256::zero(),
+        consideration_index: U256::zero(),
+        shortfall_amount: U256::zero(),
+    });
 }
 
 #[test]
diff --git a/ethers-contract/tests/it/common/derive.rs b/ethers-contract/tests/it/common/derive.rs
index 3198cc9e..6aad8e1b 100644
--- a/ethers-contract/tests/it/common/derive.rs
+++ b/ethers-contract/tests/it/common/derive.rs
@@ -1,5 +1,5 @@
 use ethers_contract::{
-    abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode,
+    abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthError, EthEvent, EthLogDecode,
 };
 use ethers_core::{
     abi::{AbiDecode, AbiEncode, RawLog, Tokenizable},
@@ -8,6 +8,7 @@ use ethers_core::{
 
 fn assert_tokenizeable<T: Tokenizable>() {}
 fn assert_ethcall<T: EthCall>() {}
+fn assert_etherror<T: EthError>() {}
 
 #[derive(Debug, Clone, PartialEq, Eq, EthAbiType)]
 struct ValueChanged {
@@ -618,3 +619,31 @@ fn can_use_result_name() {
 
     let _call = ResultCall { result: Result { result: U256::zero() } };
 }
+
+#[test]
+fn can_derive_etherror() {
+    #[derive(Debug, PartialEq, Eq, EthError)]
+    #[etherror(name = "MyError", abi = "MyError(address,address,string)")]
+    struct MyError {
+        old_author: Address,
+        addr: Address,
+        new_value: String,
+    }
+
+    assert_eq!(MyError::abi_signature().as_ref(), "MyError(address,address,string)");
+
+    assert_tokenizeable::<MyError>();
+    assert_etherror::<MyError>();
+}
+
+#[test]
+fn can_use_human_readable_error() {
+    abigen!(
+        ErrContract,
+        r#"[
+           error MyError(address,address,string)
+        ]"#,
+    );
+
+    assert_etherror::<MyError>();
+}