Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sol-macro): encode UDVTs as their underlying type in EIP-712 #220

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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