Skip to content

Commit

Permalink
fix(sol-macro): encode UDVTs as their underlying type in EIP-712 (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored Aug 2, 2023
1 parent 0ff43ed commit 4f7a72c
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 59 deletions.
2 changes: 1 addition & 1 deletion crates/dyn-abi/src/eip712/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
100 changes: 62 additions & 38 deletions crates/sol-macro/src/expand/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`]:
Expand Down Expand Up @@ -40,7 +41,7 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {
.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());

Expand Down Expand Up @@ -135,53 +136,76 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, s: &ItemStruct) -> Result<TokenStream> {
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<syn::token::Semi>,
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<TokenStream> = 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> {
<Self as ::alloy_sol_types::SolStruct>::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
}
}
13 changes: 6 additions & 7 deletions crates/sol-types/src/types/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,13 @@ impl<T: SolType, const N: usize> SolType for FixedArray<T, N> {

#[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::<Vec<u8>>();
keccak256(encoded)
.map(|element| T::eip712_data_word(element).0)
.collect::<Vec<[u8; 32]>>();
keccak256(crate::impl_core::into_flattened(encoded))
}

#[inline]
Expand Down Expand Up @@ -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()
}

Expand Down
8 changes: 2 additions & 6 deletions crates/sol-types/src/types/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<crate::private::String>(),
)
Cow::Owned(core::iter::once(root_type).chain(components).collect())
}

/// EIP-712 `typeHash`
Expand Down
18 changes: 17 additions & 1 deletion crates/sol-types/src/types/udt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<u8>) {
<$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)
}
}
};

(
Expand Down
33 changes: 27 additions & 6 deletions crates/sol-types/tests/sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
<BasicOrderParameters as SolStruct>::eip712_encode_type(),
root_type.to_string() + component
);
}

#[test]
fn eip712_encode_type_nesting() {
Expand Down
1 change: 1 addition & 0 deletions crates/syn-solidity/src/type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 4f7a72c

Please sign in to comment.