refactor(abigen): solidity types expansion (#2131)

* refactor: event input expansion

* refactor: common expand params

* refactor: method params expansion

* refactor: struct fields expansion

* refactor: types array expansion

* clippy
This commit is contained in:
DaniPopes 2023-02-14 02:15:28 +01:00 committed by GitHub
parent 0c16eb971d
commit f9ced12fee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 214 additions and 319 deletions

View File

@ -1,66 +1,8 @@
use super::{types, util, Context};
use ethers_core::{
abi::{Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
};
use super::Context;
use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote;
/// 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_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>");

View File

@ -1,7 +1,9 @@
//! derive error bindings
use super::{util, Context};
use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params};
use super::{
common::{expand_data_struct, expand_data_tuple},
types, util, Context,
};
use ethers_core::{
abi::{ethabi::AbiError, ErrorExt},
macros::{ethers_contract_crate, ethers_core_crate},
@ -81,7 +83,9 @@ impl Context {
/// 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))
types::expand_params(&error.inputs, |p| {
p.internal_type.as_deref().and_then(|s| self.internal_structs.get_struct_type(s))
})
}
/// The name ident of the errors enum

View File

@ -1,12 +1,12 @@
use super::{types, util, Context};
use crate::util::can_derive_defaults;
use ethers_core::{
abi::{Event, EventExt, EventParam, Param, ParamType},
abi::{Event, EventExt, Param},
macros::{ethers_contract_crate, ethers_core_crate},
};
use eyre::Result;
use inflector::Inflector;
use proc_macro2::{Ident, Literal, TokenStream};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::collections::BTreeMap;
@ -136,85 +136,6 @@ impl Context {
}
}
/// Expands an event property type.
///
/// Note that this is slightly different from expanding a Solidity type as
/// complex types like arrays and strings get emitted as hashes when they are
/// indexed.
/// If a complex types matches with a struct previously parsed by the internal structs,
/// we can replace it
fn expand_input_type(
&self,
event: &Event,
input: &EventParam,
idx: usize,
) -> Result<TokenStream> {
let ethers_core = ethers_core_crate();
Ok(match (&input.kind, input.indexed) {
(ParamType::Array(_), true) => {
quote! { #ethers_core::types::H256 }
}
(ParamType::FixedArray(_, _), true) => {
quote! { #ethers_core::types::H256 }
}
(ParamType::Tuple(..), true) => {
quote! { #ethers_core::types::H256 }
}
(ParamType::Bytes, true) | (ParamType::String, true) => {
quote! { #ethers_core::types::H256 }
}
(ParamType::Tuple(_), false) => {
let ty = if let Some(rust_struct_name) =
self.internal_structs.get_event_input_struct_type(&event.name, idx)
{
let ident = util::ident(rust_struct_name);
quote! {#ident}
} else {
types::expand(&input.kind)?
};
ty
}
(ParamType::Array(_), _) => {
// represents an array of a struct
if let Some(rust_struct_name) =
self.internal_structs.get_event_input_struct_type(&event.name, idx)
{
let ty = util::ident(rust_struct_name);
return Ok(quote! {::std::vec::Vec<#ty>})
}
types::expand(&input.kind)?
}
(ParamType::FixedArray(_, size), _) => {
// represents a fixed array of a struct
if let Some(rust_struct_name) =
self.internal_structs.get_event_input_struct_type(&event.name, idx)
{
let ty = util::ident(rust_struct_name);
let size = Literal::usize_unsuffixed(*size);
return Ok(quote! {[#ty; #size]})
}
types::expand(&input.kind)?
}
(kind, _) => types::expand(kind)?,
})
}
/// Expands the name-type pairs for the given inputs
fn expand_event_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
event
.inputs
.iter()
.enumerate()
.map(|(idx, input)| {
// NOTE: Events can contain nameless values.
let name = util::expand_input_name(idx, &input.name);
let ty = self.expand_input_type(event, input, idx)?;
Ok((name, ty, input.indexed))
})
.collect()
}
/// Expands into a single method for contracting an event stream.
fn expand_filter(&self, event: &Event) -> TokenStream {
let name = &event.name;
@ -251,7 +172,7 @@ impl Context {
let event_name = event_struct_name(&event.name, sig);
let params = self.expand_event_params(event)?;
let params = types::expand_event_inputs(event, &self.internal_structs)?;
// expand as a tuple if all fields are anonymous
let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields {
@ -355,6 +276,7 @@ mod tests {
use super::*;
use crate::Abigen;
use ethers_core::abi::{EventParam, Hash, ParamType};
use proc_macro2::Literal;
/// Expands a 256-bit `Hash` into a literal representation that can be used with
/// quasi-quoting for code generation. We do this to avoid allocating at runtime
@ -445,7 +367,7 @@ mod tests {
};
let cx = test_context();
let params = cx.expand_event_params(&event).unwrap();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let name = event_struct_name(&event.name, None);
let definition = expand_data_struct(&name, &params);
@ -469,7 +391,7 @@ mod tests {
};
let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_event_params(&event).unwrap();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let alias = Some(util::ident("FooAliased"));
let name = event_struct_name(&event.name, alias);
let definition = expand_data_struct(&name, &params);
@ -494,7 +416,7 @@ mod tests {
};
let cx = test_context();
let params = cx.expand_event_params(&event).unwrap();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let name = event_struct_name(&event.name, None);
let definition = expand_data_tuple(&name, &params);
@ -515,7 +437,7 @@ mod tests {
};
let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
let params = cx.expand_event_params(&event).unwrap();
let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
let alias = Some(util::ident("FooAliased"));
let name = event_struct_name(&event.name, alias);
let definition = expand_data_tuple(&name, &params);

View File

@ -1,10 +1,8 @@
use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet};
use super::{types, util, Context};
use crate::{
contract::common::{expand_data_struct, expand_data_tuple, expand_param_type, expand_params},
util::can_derive_defaults,
use super::{
common::{expand_data_struct, expand_data_tuple},
types, Context,
};
use crate::util::{self, can_derive_defaults};
use ethers_core::{
abi::{Function, FunctionExt, Param, ParamType},
macros::{ethers_contract_crate, ethers_core_crate},
@ -14,6 +12,7 @@ use eyre::{Context as _, Result};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet};
use syn::Ident;
/// The maximum amount of overloaded functions that are attempted to auto aliased with their param
@ -304,127 +303,45 @@ impl Context {
/// Expands to the `name : type` pairs of the function's inputs
fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
fun.inputs
.iter()
.enumerate()
.map(|(idx, param)| {
let name = util::expand_input_name(idx, &param.name);
let ty = self.expand_input_param_type(fun, &param.name, &param.kind)?;
Ok((name, ty))
})
.collect()
}
/// Expands to the `name : type` pairs of the function's outputs
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
expand_params(&fun.outputs, |s| {
self.internal_structs.get_function_output_struct_type(&fun.name, s)
types::expand_params(&fun.inputs, |p| {
self.internal_structs.get_function_input_struct_type(&fun.name, &p.name)
})
}
/// Expands to the return type of a function
fn expand_outputs(&self, fun: &Function) -> Result<TokenStream> {
let mut outputs = Vec::with_capacity(fun.outputs.len());
for param in fun.outputs.iter() {
let ty = self.expand_output_param_type(fun, param, &param.kind)?;
outputs.push(ty);
}
let return_ty = match outputs.len() {
0 => quote! { () },
1 => outputs[0].clone(),
_ => {
quote! { (#( #outputs ),*) }
}
};
Ok(return_ty)
/// Expands to the `name: type` pairs of the function's outputs
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
types::expand_params(&fun.outputs, |p| {
p.internal_type
.as_deref()
.and_then(|s| self.internal_structs.get_function_output_struct_type(&fun.name, s))
})
}
/// Expands the arguments for the call that eventually calls the contract
fn expand_contract_call_args(&self, fun: &Function) -> Result<TokenStream> {
let mut call_args = Vec::with_capacity(fun.inputs.len());
for (idx, param) in fun.inputs.iter().enumerate() {
fn expand_contract_call_args(&self, fun: &Function) -> TokenStream {
let mut call_args = fun.inputs.iter().enumerate().map(|(idx, param)| {
let name = util::expand_input_name(idx, &param.name);
let call_arg = match param.kind {
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`
// 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,)}
quote!((#name,))
}
_ => name,
};
call_args.push(call_arg);
}
});
match fun.inputs.len() {
0 => quote!(()),
1 => call_args.next().unwrap(),
_ => quote!(( #( #call_args ),* )),
}
let call_args = match call_args.len() {
0 => quote! { () },
1 => quote! { #( #call_args )* },
_ => quote! { ( #(#call_args, )* ) },
};
Ok(call_args)
}
/// returns the Tokenstream for the corresponding rust type of the param
fn expand_input_param_type(
&self,
fun: &Function,
param: &str,
kind: &ParamType,
) -> Result<TokenStream> {
let ethers_core = ethers_core_crate();
match kind {
ParamType::Array(ty) => {
let ty = self.expand_input_param_type(fun, param, ty)?;
Ok(quote! {
::std::vec::Vec<#ty>
})
}
ParamType::FixedArray(ty, size) => {
let ty = match **ty {
ParamType::Uint(size) => {
if size / 8 == 1 {
// this prevents type ambiguity with `FixedBytes`
quote! { #ethers_core::types::Uint8}
} else {
self.expand_input_param_type(fun, param, ty)?
}
}
_ => self.expand_input_param_type(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),
}
}
/// 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> {
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
@ -439,12 +356,22 @@ impl Context {
let selector_tokens = expand_selector(selector);
let contract_args = self.expand_contract_call_args(function)?;
let contract_args = self.expand_contract_call_args(function);
let function_params =
self.expand_input_params(function)?.into_iter().map(|(name, ty)| quote! { #name: #ty });
let function_params = quote! { #( , #function_params )* };
let outputs = self.expand_outputs(function)?;
let outputs = {
let mut out = self.expand_output_params(function)?;
match out.len() {
0 => quote!(()),
1 => out.pop().unwrap().1,
_ => {
let iter = out.into_iter().map(|(_, ty)| ty);
quote!(( #( #iter ),* ))
}
}
};
let doc_str =
format!("Calls the contract's `{name}` (0x{}) function", hex::encode(selector));

View File

@ -93,7 +93,7 @@ impl Context {
for field in sol_struct.fields() {
let ty = match field.r#type() {
FieldType::Elementary(ty) => types::expand(ty)?,
FieldType::Struct(struct_ty) => expand_struct_type(struct_ty),
FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty),
FieldType::Mapping(_) => {
eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field)
}
@ -154,7 +154,7 @@ impl Context {
fields.push(quote! { pub #field_name: #ty });
}
FieldType::Struct(struct_ty) => {
let ty = expand_struct_type(struct_ty);
let ty = types::expand_struct_type(struct_ty);
fields.push(quote! { pub #field_name: #ty });
let name = struct_ty.name();
@ -592,24 +592,6 @@ fn struct_type_projections(name: &str) -> Vec<String> {
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().to_pascal_case());
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::*;

View File

@ -1,9 +1,17 @@
use ethers_core::{abi::ParamType, macros::ethers_core_crate};
use eyre::{bail, Result};
use proc_macro2::{Literal, TokenStream};
use quote::quote;
//! Types expansion
pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
use crate::{util, InternalStructs};
use ethers_core::{
abi::{struct_def::StructFieldType, Event, EventParam, Param, ParamType},
macros::ethers_core_crate,
};
use eyre::{bail, Result};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::{quote, ToTokens};
/// Expands a ParamType Solidity type to its Rust equivalent.
pub fn expand(kind: &ParamType) -> Result<TokenStream> {
let ethers_core = ethers_core_crate();
match kind {
@ -16,7 +24,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
5..=8 => Ok(quote! { i64 }),
9..=16 => Ok(quote! { i128 }),
17..=32 => Ok(quote! { #ethers_core::types::I256 }),
_ => bail!("unsupported solidity type int{}", n),
_ => bail!("unsupported solidity type int{n}"),
},
ParamType::Uint(n) => match n / 8 {
1 => Ok(quote! { u8 }),
@ -25,41 +33,151 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
5..=8 => Ok(quote! { u64 }),
9..=16 => Ok(quote! { u128 }),
17..=32 => Ok(quote! { #ethers_core::types::U256 }),
_ => bail!("unsupported solidity type uint{}", n),
_ => bail!("unsupported solidity type uint{n}"),
},
ParamType::Bool => Ok(quote! { bool }),
ParamType::String => Ok(quote! { String }),
ParamType::Array(t) => {
let inner = expand(t)?;
Ok(quote! { Vec<#inner> })
}
ParamType::FixedBytes(n) => {
// TODO(nlordell): what is the performance impact of returning large
// `FixedBytes` and `FixedArray`s with `web3`?
let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [u8; #size] })
}
ParamType::FixedArray(t, n) => {
// TODO(nlordell): see above
let inner = match **t {
ParamType::Uint(size) => {
if size / 8 == 1 {
// this prevents type ambiguity with `FixedBytes`
quote! { #ethers_core::types::Uint8}
} else {
expand(t)?
}
}
_ => expand(t)?,
ParamType::Bool => Ok(quote!(bool)),
ParamType::String => Ok(quote!(::std::string::String)),
ParamType::Array(ty) => Ok(array(expand(ty)?, None)),
ParamType::FixedBytes(n) => Ok(array(quote!(u8), Some(*n))),
ParamType::FixedArray(ty, n) => {
let ty = match **ty {
// this prevents type ambiguity with `FixedBytes`
// see: https://github.com/gakonst/ethers-rs/issues/1636
ParamType::Uint(size) if size / 8 == 1 => quote!(#ethers_core::types::Uint8),
_ => expand(ty)?,
};
let size = Literal::usize_unsuffixed(*n);
Ok(quote! { [#inner; #size] })
Ok(array(ty, Some(*n)))
}
ParamType::Tuple(members) => {
eyre::ensure!(!members.is_empty(), "Tuple must have at least 1 member");
let members = members.iter().map(expand).collect::<Result<Vec<_>, _>>()?;
Ok(quote! { (#(#members,)*) })
Ok(quote!(( #( #members ),* )))
}
}
}
/// Expands the event's inputs.
pub fn expand_event_inputs(
event: &Event,
internal_structs: &InternalStructs,
) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
event
.inputs
.iter()
.enumerate()
.map(|(index, input)| {
// NOTE: Events can contain nameless values.
expand_event_input(input, &event.name, index, internal_structs)
.map(|ty| (util::expand_input_name(index, &input.name), ty, input.indexed))
})
.collect()
}
/// Expands an event property type.
///
/// Note that this is slightly different from expanding a Solidity type as complex types like arrays
/// and strings get emitted as hashes when they are indexed.
///
/// If a complex types matches with a struct previously parsed by the internal structs, we can
/// replace it.
fn expand_event_input(
input: &EventParam,
name: &str,
index: usize,
internal_structs: &InternalStructs,
) -> Result<TokenStream> {
let kind = &input.kind;
match (kind, input.indexed) {
(ParamType::Array(_), true) |
(ParamType::FixedArray(_, _), true) |
(ParamType::Tuple(_), true) |
(ParamType::Bytes, true) |
(ParamType::String, true) => {
let ethers_core = ethers_core_crate();
Ok(quote!(#ethers_core::types::H256))
}
(ParamType::Array(_), false) |
(ParamType::FixedArray(_, _), false) |
(ParamType::Tuple(_), false) => {
match internal_structs.get_event_input_struct_type(name, index) {
Some(ty) => {
let ty = util::ident(ty);
match kind {
ParamType::Array(_) => Ok(array(ty, None)),
ParamType::FixedArray(_, size) => Ok(array(ty, Some(*size))),
ParamType::Tuple(_) => Ok(quote!(#ty)),
_ => unreachable!(),
}
}
None => expand(kind),
}
}
_ => expand(kind),
}
}
/// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given
/// function.
pub fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
params: &'a [Param],
resolve_tuple: F,
) -> Result<Vec<(TokenStream, TokenStream)>> {
params
.iter()
.enumerate()
.map(|(idx, param)| {
// NOTE: Params can be unnamed.
expand_resolved(&param.kind, param, &resolve_tuple)
.map(|ty| (util::expand_input_name(idx, &param.name), ty))
})
.collect()
}
/// Expands a ParamType Solidity type to its Rust equivalent, while resolving tuples' types using
/// the given function.
fn expand_resolved<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
kind: &'a ParamType,
param: &'a Param,
resolve_tuple: &F,
) -> Result<TokenStream> {
match kind {
ParamType::Array(ty) => Ok(array(expand_resolved(ty, param, resolve_tuple)?, None)),
ParamType::FixedArray(ty, size) => {
Ok(array(expand_resolved(ty, param, resolve_tuple)?, Some(*size)))
}
ParamType::Tuple(_) => match resolve_tuple(param) {
Some(ty) => {
let ty = util::ident(ty);
Ok(quote!(#ty))
}
None => expand(kind),
},
_ => expand(kind),
}
}
/// Expands to the Rust struct type.
pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
match struct_ty {
StructFieldType::Type(ty) => {
let ty = util::ident(&ty.name().to_pascal_case());
quote!(#ty)
}
StructFieldType::Array(ty) => array(expand_struct_type(ty), None),
StructFieldType::FixedArray(ty, size) => array(expand_struct_type(ty), Some(*size)),
}
}
/// Expands `ty` into a Rust array or vector.
fn array<T: ToTokens>(ty: T, size: Option<usize>) -> TokenStream {
match size {
Some(size) => {
let size = Literal::usize_unsuffixed(size);
quote!([#ty; #size])
}
None => quote!(::std::vec::Vec<#ty>),
}
}