diff --git a/crates/dyn-abi/src/eip712/resolver.rs b/crates/dyn-abi/src/eip712/resolver.rs index ff8c4a7362..6e6494a0e3 100644 --- a/crates/dyn-abi/src/eip712/resolver.rs +++ b/crates/dyn-abi/src/eip712/resolver.rs @@ -325,7 +325,7 @@ impl Resolver { let type_name = type_def.type_name.to_owned(); // Insert the edges into the graph { - let entry = self.edges.entry(type_name.clone()).or_insert_with(Vec::new); + let entry = self.edges.entry(type_name.clone()).or_default(); type_def .props .iter() diff --git a/crates/sol-macro/src/expand/struct.rs b/crates/sol-macro/src/expand/struct.rs index 2b9ca8b808..ce32f42002 100644 --- a/crates/sol-macro/src/expand/struct.rs +++ b/crates/sol-macro/src/expand/struct.rs @@ -3,9 +3,10 @@ use super::{ expand_fields, expand_from_into_tuples, expand_type, ty::expand_tokenize_func, ExpCtxt, }; -use ast::{ItemStruct, VariableDeclaration}; +use ast::{Item, ItemStruct, Type, VariableDeclaration}; use proc_macro2::TokenStream; use quote::quote; +use std::num::NonZeroU16; use syn::Result; /// Expands an [`ItemStruct`]: @@ -40,7 +41,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { .map(|f| (expand_type(&f.ty), f.name.as_ref().unwrap())) .unzip(); - let eip712_encode_type_fns = expand_encode_type_fns(fields, name); + let eip712_encode_type_fns = expand_encode_type_fns(cx, fields, name); let tokenize_impl = expand_tokenize_func(fields.iter()); @@ -135,53 +136,76 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { Ok(tokens) } -fn expand_encode_type_fns(fields: &ast::FieldList, name: &ast::SolIdent) -> TokenStream { - let components_impl = expand_eip712_components(fields); - let root_type_impl = fields.eip712_signature(name.as_string()); - - let encode_type_impl_opt = if fields.iter().any(|f| f.ty.is_custom()) { - None - } else { - Some(quote! { - fn eip712_encode_type() -> ::alloy_sol_types::private::Cow<'static, str> { - Self::eip712_root_type() - } - }) - }; - - quote! { - fn eip712_components() -> ::alloy_sol_types::private::Vec<::alloy_sol_types::private::Cow<'static, str>> { - #components_impl +fn expand_encode_type_fns( + cx: &ExpCtxt<'_>, + fields: &ast::Parameters, + name: &ast::SolIdent, +) -> TokenStream { + // account for UDVTs and enums which do not implement SolStruct + let mut fields = fields.clone(); + fields.visit_types_mut(|ty| { + let Type::Custom(name) = ty else { return }; + match cx.try_get_item(name) { + // keep as custom + Some(Item::Struct(_)) | None => {} + // convert to underlying + Some(Item::Enum(_)) => *ty = Type::Uint(ty.span(), NonZeroU16::new(8)), + Some(Item::Udt(udt)) => *ty = udt.ty.clone(), + Some(item) => panic!("Invalid type in struct field: {item:?}"), } + }); - fn eip712_root_type() -> ::alloy_sol_types::private::Cow<'static, str> { - #root_type_impl.into() - } + let root = fields.eip712_signature(name.as_string()); - #encode_type_impl_opt - } -} + let custom = fields.iter().filter(|f| f.ty.has_custom()); + let n_custom = custom.clone().count(); + + let components_impl = if n_custom > 0 { + let bits = custom.map(|field| { + // need to recurse to find the inner custom type + let mut ty = None; + field.ty.visit(|field_ty| { + if ty.is_none() && field_ty.is_custom() { + ty = Some(field_ty.clone()); + } + }); + // cannot panic as this field is guaranteed to contain a custom type + let ty = expand_type(&ty.unwrap()); -fn expand_eip712_components(fields: &ast::FieldList) -> TokenStream { - let bits: Vec = fields - .iter() - .filter(|f| f.ty.is_custom()) - .map(|field| { - let ty = expand_type(&field.ty); quote! { components.push(<#ty as ::alloy_sol_types::SolStruct>::eip712_root_type()); components.extend(<#ty as ::alloy_sol_types::SolStruct>::eip712_components()); } - }) - .collect(); - - if bits.is_empty() { - quote! { ::alloy_sol_types::private::Vec::new() } - } else { + }); + let capacity = proc_macro2::Literal::usize_unsuffixed(n_custom); quote! { - let mut components = ::alloy_sol_types::private::Vec::new(); + let mut components = ::alloy_sol_types::private::Vec::with_capacity(#capacity); #(#bits)* components } + } else { + quote! { ::alloy_sol_types::private::Vec::new() } + }; + + let encode_type_impl_opt = (n_custom == 0).then(|| { + quote! { + #[inline] + fn eip712_encode_type() -> ::alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + } + }); + + quote! { + #[inline] + fn eip712_root_type() -> ::alloy_sol_types::private::Cow<'static, str> { + ::alloy_sol_types::private::Cow::Borrowed(#root) + } + + fn eip712_components() -> ::alloy_sol_types::private::Vec<::alloy_sol_types::private::Cow<'static, str>> { + #components_impl + } + + #encode_type_impl_opt } } diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index c663d3339a..8648d971ca 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -496,12 +496,13 @@ impl SolType for FixedArray { #[inline] fn eip712_data_word(rust: &Self::RustType) -> Word { - let rust = rust; + // TODO: collect into an array of [u8; 32] and flatten it to a slice like in + // tuple impl let encoded = rust .iter() - .flat_map(|element| T::eip712_data_word(element).0) - .collect::>(); - keccak256(encoded) + .map(|element| T::eip712_data_word(element).0) + .collect::>(); + keccak256(crate::impl_core::into_flattened(encoded)) } #[inline] @@ -609,9 +610,7 @@ macro_rules! tuple_impls { <$ty as SolType>::eip712_data_word($ty).0, )+]; // SAFETY: Flattening [[u8; 32]; COUNT] to [u8; COUNT * 32] is valid - let ptr = encoding.as_ptr() as *const u8; - let len = COUNT * 32; - let encoding: &[u8] = unsafe { core::slice::from_raw_parts(ptr, len) }; + let encoding: &[u8] = unsafe { core::slice::from_raw_parts(encoding.as_ptr().cast(), COUNT * 32) }; keccak256(encoding).into() } diff --git a/crates/sol-types/src/types/struct.rs b/crates/sol-types/src/types/struct.rs index 0d492e0f5d..0640a2119d 100644 --- a/crates/sol-types/src/types/struct.rs +++ b/crates/sol-types/src/types/struct.rs @@ -82,13 +82,9 @@ pub trait SolStruct: 'static { return root_type } - components.sort(); + components.sort_unstable(); components.dedup(); - Cow::Owned( - core::iter::once(root_type) - .chain(components) - .collect::(), - ) + Cow::Owned(core::iter::once(root_type).chain(components).collect()) } /// EIP-712 `typeHash` diff --git a/crates/sol-types/src/types/udt.rs b/crates/sol-types/src/types/udt.rs index 53aaa4765d..9b17d5e8df 100644 --- a/crates/sol-types/src/types/udt.rs +++ b/crates/sol-types/src/types/udt.rs @@ -11,7 +11,6 @@ macro_rules! define_udt { underlying: $underlying:ty, type_check: $path:path, ) => { - $(#[$outer])* /// This struct is a Solidity user-defined value type. It wraps /// an underlying type. @@ -92,6 +91,23 @@ macro_rules! define_udt { <$underlying as $crate::SolType>::encode_packed_to(rust, out) } } + + impl $crate::EventTopic for $name { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + <$underlying as $crate::EventTopic>::topic_preimage_length(rust) + } + + #[inline] + fn encode_topic_preimage(rust: &Self::RustType, out: &mut $crate::private::Vec) { + <$underlying as $crate::EventTopic>::encode_topic_preimage(rust, out) + } + + #[inline] + fn encode_topic(rust: &Self::RustType) -> $crate::token::WordToken { + <$underlying as $crate::EventTopic>::encode_topic(rust) + } + } }; ( diff --git a/crates/sol-types/tests/sol.rs b/crates/sol-types/tests/sol.rs index 2ea3300456..b4764fd4e0 100644 --- a/crates/sol-types/tests/sol.rs +++ b/crates/sol-types/tests/sol.rs @@ -283,12 +283,33 @@ fn abigen_json_large_array() { ); } -// TODO -// #[test] -// #[cfg(feature = "json")] -// fn abigen_json_seaport() { -// sol!(Seaport, "../json-abi/tests/abi/Seaport.json"); -// } +#[test] +#[cfg(feature = "json")] +fn abigen_json_seaport() { + use alloy_sol_types::SolStruct; + use std::borrow::Cow; + use Seaport::*; + + sol!(Seaport, "../json-abi/tests/abi/Seaport.json"); + + // BasicOrderType is a uint8 UDVT + let _ = BasicOrderType::from(0u8); + + // BasicOrderParameters is a struct that contains UDVTs (basicOrderType) and a + // struct array. The only component should be the struct of the struct array. + let root_type = "BasicOrderParameters(address considerationToken,uint256 considerationIdentifier,uint256 considerationAmount,address offerer,address zone,address offerToken,uint256 offerIdentifier,uint256 offerAmount,uint8 basicOrderType,uint256 startTime,uint256 endTime,bytes32 zoneHash,uint256 salt,bytes32 offererConduitKey,bytes32 fulfillerConduitKey,uint256 totalOriginalAdditionalRecipients,AdditionalRecipient[] additionalRecipients,bytes signature)"; + let component = "AdditionalRecipient(uint256 amount,address recipient)"; + + assert_eq!(BasicOrderParameters::eip712_root_type(), root_type); + assert_eq!( + BasicOrderParameters::eip712_components(), + [Cow::Borrowed(component)] + ); + assert_eq!( + ::eip712_encode_type(), + root_type.to_string() + component + ); +} #[test] fn eip712_encode_type_nesting() { diff --git a/crates/syn-solidity/src/type/mod.rs b/crates/syn-solidity/src/type/mod.rs index e7ebdacd26..f0927330f6 100644 --- a/crates/syn-solidity/src/type/mod.rs +++ b/crates/syn-solidity/src/type/mod.rs @@ -263,6 +263,7 @@ impl Type { matches!(self, Self::Custom(_)) } + /// Recurses into this type and returns whether it contains a custom type. pub fn has_custom(&self) -> bool { match self { Self::Custom(_) => true,