diff --git a/crates/sol-macro/src/expand/struct.rs b/crates/sol-macro/src/expand/struct.rs index ce458e6e0..2acebb6fb 100644 --- a/crates/sol-macro/src/expand/struct.rs +++ b/crates/sol-macro/src/expand/struct.rs @@ -40,22 +40,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { .map(|f| (expand_type(&f.ty), f.name.as_ref().unwrap())) .unzip(); - let encoded_type = fields.eip712_signature(name.as_string()); - let encode_type_impl = if fields.iter().any(|f| f.ty.is_custom()) { - quote! { - { - let mut encoded = String::from(#encoded_type); - #( - if let Some(s) = <#field_types as ::alloy_sol_types::SolType>::eip712_encode_type() { - encoded.push_str(&s); - } - )* - encoded - } - } - } else { - quote!(#encoded_type) - }; + let eip712_encode_type_fns: TokenStream = expand_encode_type_fns(fields, name); let tokenize_impl = expand_tokenize_func(fields.iter()); @@ -109,9 +94,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { #tokenize_impl } - fn eip712_encode_type() -> ::alloy_sol_types::private::Cow<'static, str> { - #encode_type_impl.into() - } + #eip712_encode_type_fns fn eip712_encode_data(&self) -> Vec { #encode_data_impl @@ -151,3 +134,57 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result { }; Ok(tokens) } + +fn expand_encode_type_fns( + fields: &ast::Parameters, + 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: Option = 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 eip712_root_type() -> ::alloy_sol_types::private::Cow<'static, str> { + #root_type_impl.into() + } + + #encode_type_impl_opt + } +} + +fn expand_eip712_components(fields: &ast::Parameters) -> 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::with_capacity(0) } + } else { + quote! { + let mut components = ::alloy_sol_types::private::Vec::new(); + #(#bits)* + components + } + } +} diff --git a/crates/sol-types/src/types/struct.rs b/crates/sol-types/src/types/struct.rs index c9e18e4d4..0d492e0f5 100644 --- a/crates/sol-types/src/types/struct.rs +++ b/crates/sol-types/src/types/struct.rs @@ -40,7 +40,7 @@ pub trait SolStruct: 'static { /// The struct name. /// - /// Used in [`eip712_encode_type`][SolStruct::eip712_encode_type]. + /// Used in [`eip712_encode_type`][SolType::sol_type_name]. const NAME: &'static str; // TODO: avoid clones here @@ -63,9 +63,33 @@ pub trait SolStruct: 'static { self.tokenize().total_words() * Word::len_bytes() } + /// Returns component EIP-712 types. These types are used to construct + /// the `encodeType` string. These are the types of the struct's fields, + /// and should not include the root type. + fn eip712_components() -> Vec>; + + /// Return the root EIP-712 type. This type is used to construct the + /// `encodeType` string. + fn eip712_root_type() -> Cow<'static, str>; + /// EIP-712 `encodeType` /// - fn eip712_encode_type() -> Cow<'static, str>; + fn eip712_encode_type() -> Cow<'static, str> { + let root_type = Self::eip712_root_type(); + let mut components = Self::eip712_components(); + + if components.is_empty() { + return root_type + } + + components.sort(); + components.dedup(); + Cow::Owned( + core::iter::once(root_type) + .chain(components) + .collect::(), + ) + } /// EIP-712 `typeHash` /// diff --git a/crates/sol-types/tests/sol.rs b/crates/sol-types/tests/sol.rs index 2674befc9..dd0abd1fb 100644 --- a/crates/sol-types/tests/sol.rs +++ b/crates/sol-types/tests/sol.rs @@ -282,3 +282,38 @@ fn abigen_json() { "callWithLongArray(uint64[128])" ); } + +#[test] +fn eip712_encode_type_nesting() { + sol! { + struct A { + uint256 a; + } + + struct B { + bytes32 b; + } + + struct C { + A a; + B b; + } + + struct D { + C c; + A a; + B b; + } + } + + assert_eq!(A::eip712_encode_type().unwrap(), "A(uint256 a)"); + assert_eq!(B::eip712_encode_type().unwrap(), "B(bytes32 b)"); + assert_eq!( + C::eip712_encode_type().unwrap(), + "C(A a,B b)A(uint256 a)B(bytes32 b)" + ); + assert_eq!( + D::eip712_encode_type().unwrap(), + "D(C c,A a,B b)A(uint256 a)B(bytes32 b)C(A a,B b)" + ); +}