diff --git a/crates/sol-macro/src/expand/mod.rs b/crates/sol-macro/src/expand/mod.rs index 8d22f88235..51749aaf90 100644 --- a/crates/sol-macro/src/expand/mod.rs +++ b/crates/sol-macro/src/expand/mod.rs @@ -1,12 +1,12 @@ //! Functions which generate Rust code from the Solidity AST. use ast::{ - File, Item, ItemContract, ItemError, ItemFunction, ItemStruct, ItemUdt, Parameters, SolIdent, - Type, VariableDeclaration, Visit, + EventParameter, File, Item, ItemContract, ItemError, ItemEvent, ItemFunction, ItemStruct, + ItemUdt, Parameters, SolIdent, Type, VariableDeclaration, Visit, }; use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, IdentFragment}; -use std::{collections::HashMap, fmt::Write}; +use quote::{format_ident, quote, quote_spanned, IdentFragment}; +use std::{borrow::Borrow, collections::HashMap, fmt::Write}; use syn::{parse_quote, Attribute, Error, Result, Token}; mod attr; @@ -23,14 +23,28 @@ pub fn expand(ast: File) -> Result { ExpCtxt::new(&ast).expand() } -fn expand_var(var: &VariableDeclaration) -> TokenStream { - let VariableDeclaration { ty, name, .. } = var; +fn expand_fields

(params: &Parameters

) -> impl Iterator + '_ { + params + .iter() + .enumerate() + .map(|(i, var)| expand_field(i, &var.ty, var.name.as_ref())) +} + +fn expand_field(i: usize, ty: &Type, name: Option<&SolIdent>) -> TokenStream { + let name = anon_name((i, name)); let ty = expand_type(ty); quote! { #name: <#ty as ::alloy_sol_types::SolType>::RustType } } +fn anon_name + Clone>((i, name): (usize, Option<&T>)) -> Ident { + match name { + Some(name) => name.clone().into(), + None => format_ident!("_{i}"), + } +} + struct ExpCtxt<'ast> { all_items: Vec<&'ast Item>, custom_types: HashMap, @@ -80,6 +94,7 @@ impl<'ast> ExpCtxt<'ast> { match item { Item::Contract(contract) => self.expand_contract(contract), Item::Error(error) => self.expand_error(error), + Item::Event(event) => self.expand_event(event), Item::Function(function) => self.expand_function(function), Item::Struct(s) => self.expand_struct(s), Item::Udt(udt) => self.expand_udt(udt), @@ -167,7 +182,7 @@ impl<'ast> ExpCtxt<'ast> { let variants: Vec<_> = errors.iter().map(|error| error.name.0.clone()).collect(); let min_data_len = errors .iter() - .map(|error| self.min_data_size(&error.fields)) + .map(|error| self.min_data_size(&error.parameters)) .max() .unwrap(); let trt = Ident::new("SolError", Span::call_site()); @@ -189,9 +204,9 @@ impl<'ast> ExpCtxt<'ast> { let min_data_len = min_data_len.min(4); quote! { #(#attrs)* - pub enum #name {#( - #variants(#types), - )*} + pub enum #name { + #(#variants(#types),)* + } // TODO: Implement these functions using traits? #[automatically_derived] @@ -254,20 +269,20 @@ impl<'ast> ExpCtxt<'ast> { fn expand_error(&self, error: &ItemError) -> Result { let ItemError { - fields, + parameters, name, attrs, .. } = error; - self.assert_resolved(fields)?; + self.assert_resolved(parameters)?; - let signature = self.signature(name.as_string(), fields); + let signature = self.signature(name.as_string(), parameters); let selector = crate::utils::selector(&signature); - let size = self.params_data_size(fields, None); + let size = self.params_data_size(parameters, None); - let converts = expand_from_into_tuples(&name.0, fields); - let fields = fields.iter().map(expand_var); + let converts = expand_from_into_tuples(&name.0, parameters); + let fields = expand_fields(parameters); let tokens = quote! { #(#attrs)* #[allow(non_camel_case_types, non_snake_case)] @@ -283,7 +298,7 @@ impl<'ast> ExpCtxt<'ast> { #[automatically_derived] impl ::alloy_sol_types::SolError for #name { type Tuple = UnderlyingSolTuple; - type Token = ::TokenType; + type Token = ::TokenType; const SIGNATURE: &'static str = #signature; const SELECTOR: [u8; 4] = #selector; @@ -305,6 +320,136 @@ impl<'ast> ExpCtxt<'ast> { Ok(tokens) } + fn expand_event(&self, event: &ItemEvent) -> Result { + let ItemEvent { name, attrs, .. } = event; + let parameters = event.params(); + + self.assert_resolved(¶meters)?; + event.assert_valid()?; + + let signature = self.signature(name.as_string(), ¶meters); + let selector = crate::utils::event_selector(&signature); + let anonymous = event.is_anonymous(); + + // prepend the first topic if not anonymous + let first_topic = (!anonymous).then(|| quote!(::alloy_sol_types::sol_data::FixedBytes<32>)); + let topic_list = event + .indexed_params() + .map(|param| self.expand_event_topic_type(param)); + let topic_list = first_topic.into_iter().chain(topic_list); + + let (data_tuple, _) = expand_tuple_types(event.dynamic_params().map(|p| &p.ty)); + let data_size = self.params_data_size(event.dynamic_params().map(|p| p.as_param()), None); + + // skip first topic if not anonymous, which is the hash of the signature + let mut topic_i = !anonymous as usize; + let mut data_i = 0usize; + let new_impl = event.parameters.iter().enumerate().map(|(i, p)| { + let name = anon_name((i, p.name.as_ref())); + let param; + if p.is_dynamic() { + let i = syn::Index::from(data_i); + param = quote!(data.#i); + data_i += 1; + } else { + let i = syn::Index::from(topic_i); + param = quote!(topics.#i); + topic_i += 1; + } + quote!(#name: #param) + }); + + let data_tuple_names = event + .dynamic_params() + .map(|p| p.name.as_ref()) + .enumerate() + .map(anon_name); + + let encode_first_topic = + (!anonymous).then(|| quote!(::alloy_sol_types::token::WordToken(Self::SIGNATURE_HASH))); + let encode_topics_impl = event.indexed_params().enumerate().map(|(i, p)| { + let name = anon_name((i, p.name.as_ref())); + let ty = expand_type(&p.ty); + quote! { + <#ty as ::alloy_sol_types::EventTopic>::encode_topic(&self.#name) + } + }); + let encode_topics_impl = encode_first_topic + .into_iter() + .chain(encode_topics_impl) + .enumerate() + .map(|(i, assign)| quote!(out[#i] = #assign;)); + + let fields = expand_fields(¶meters); + let tokens = quote! { + #(#attrs)* + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct #name { + #(pub #fields,)* + } + + #[allow(non_camel_case_types, non_snake_case, clippy::style)] + const _: () = { + impl ::alloy_sol_types::SolEvent for #name { + type DataTuple = #data_tuple; + type DataToken = ::TokenType; + + type TopicList = (#(#topic_list,)*); + + const SIGNATURE: &'static str = #signature; + const SIGNATURE_HASH: ::alloy_sol_types::B256 = + ::alloy_sol_types::Word::new(#selector); + + const ANONYMOUS: bool = #anonymous; + + fn new( + topics: ::RustType, + data: ::RustType, + ) -> Self { + Self { + #(#new_impl,)* + } + } + + fn data_size(&self) -> usize { + #data_size + } + + fn encode_data_raw(&self, out: &mut Vec) { + out.reserve(self.data_size()); + out.extend( + ::encode( + // TODO: Avoid cloning + (#(self.#data_tuple_names.clone(),)*) + ) + ); + } + + fn encode_topics_raw( + &self, + out: &mut [::alloy_sol_types::token::WordToken], + ) -> ::alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(::alloy_sol_types::Error::Overrun); + } + #(#encode_topics_impl)* + Ok(()) + } + } + }; + }; + Ok(tokens) + } + + fn expand_event_topic_type(&self, param: &EventParameter) -> TokenStream { + debug_assert!(param.is_indexed()); + if param.is_dynamic() { + quote_spanned! {param.ty.span()=> ::alloy_sol_types::sol_data::FixedBytes<32> } + } else { + expand_type(¶m.ty) + } + } + fn expand_function(&self, function: &ItemFunction) -> Result { let function_name = self.function_name(function); let call_name = self.call_name(function_name.clone()); @@ -328,7 +473,7 @@ impl<'ast> ExpCtxt<'ast> { ) -> Result { self.assert_resolved(params)?; - let fields = params.iter().map(expand_var); + let fields = expand_fields(params); let signature = self.signature(function.name.as_string(), params); let selector = crate::utils::selector(&signature); @@ -353,7 +498,7 @@ impl<'ast> ExpCtxt<'ast> { #[automatically_derived] impl ::alloy_sol_types::SolCall for #call_name { type Tuple = UnderlyingSolTuple; - type Token = ::TokenType; + type Token = ::TokenType; const SIGNATURE: &'static str = #signature; const SELECTOR: [u8; 4] = #selector; @@ -383,22 +528,21 @@ impl<'ast> ExpCtxt<'ast> { .. } = s; - let (f_ty, f_name): (Vec<_>, Vec<_>) = s - .fields + let field_types_s = fields.iter().map(|f| f.ty.to_string()); + let field_names_s = fields.iter().map(|f| f.name.as_ref().unwrap().to_string()); + + let (field_types, field_names): (Vec<_>, Vec<_>) = fields .iter() - .map(|f| (f.ty.to_string(), f.name.as_ref().unwrap().to_string())) + .map(|f| (expand_type(&f.ty), f.name.as_ref().unwrap())) .unzip(); - let props_tys: Vec<_> = fields.iter().map(|f| expand_type(&f.ty)).collect(); - let props = fields.iter().map(|f| &f.name); - - let encoded_type = fields.eip712_signature(name.to_string()); + 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) = <#props_tys as ::alloy_sol_types::SolType>::eip712_encode_type() { + if let Some(s) = <#field_types as ::alloy_sol_types::SolType>::eip712_encode_type() { encoded.push_str(&s); } )* @@ -418,7 +562,7 @@ impl<'ast> ExpCtxt<'ast> { } _ => quote! { [#( - <#props_tys as ::alloy_sol_types::SolType>::eip712_data_word(&self.#props).0, + <#field_types as ::alloy_sol_types::SolType>::eip712_data_word(&self.#field_names).0, )*].concat() }, }; @@ -426,7 +570,7 @@ impl<'ast> ExpCtxt<'ast> { let attrs = attrs.iter(); let convert = expand_from_into_tuples(&name.0, fields); let name_s = name.to_string(); - let fields = fields.iter().map(expand_var); + let fields = expand_fields(fields); let tokens = quote! { #(#attrs)* #[allow(non_camel_case_types, non_snake_case)] @@ -444,12 +588,12 @@ impl<'ast> ExpCtxt<'ast> { #[automatically_derived] impl ::alloy_sol_types::SolStruct for #name { type Tuple = UnderlyingSolTuple; - type Token = ::TokenType; + type Token = ::TokenType; const NAME: &'static str = #name_s; const FIELDS: &'static [(&'static str, &'static str)] = &[ - #((#f_ty, #f_name)),* + #((#field_types_s, #field_names_s)),* ]; fn to_rust(&self) -> UnderlyingRustTuple { @@ -468,6 +612,38 @@ impl<'ast> ExpCtxt<'ast> { #encode_data_impl } } + + #[automatically_derived] + impl ::alloy_sol_types::EventTopic for #name { + #[inline] + fn topic_preimage_length>(rust: B) -> usize { + let b = rust.borrow(); + 0usize + #( + + <#field_types as ::alloy_sol_types::EventTopic>::topic_preimage_length(&b.#field_names) + )* + } + + #[inline] + fn encode_topic_preimage>(rust: B, out: &mut Vec) { + let b = rust.borrow(); + out.reserve(::topic_preimage_length(b)); + #( + <#field_types as ::alloy_sol_types::EventTopic>::encode_topic_preimage(&b.#field_names, out); + )* + } + + #[inline] + fn encode_topic>( + rust: B + ) -> ::alloy_sol_types::token::WordToken { + let mut out = Vec::new(); + ::encode_topic_preimage(rust, &mut out); + ::alloy_sol_types::token::WordToken( + ::alloy_sol_types::keccak256(out) + ) + } + } }; }; Ok(tokens) @@ -617,14 +793,19 @@ impl<'ast> ExpCtxt<'ast> { format_ident!("{function_name}Return") } - fn signature

(&self, mut name: String, params: &Parameters

) -> String { - name.reserve(2 + params.len() * 16); + fn signature<'a, I: IntoIterator>( + &self, + mut name: String, + params: I, + ) -> String { name.push('('); - for (i, param) in params.iter().enumerate() { - if i > 0 { + let mut first = true; + for param in params { + if !first { name.push(','); } write!(name, "{}", TypePrinter::new(self, ¶m.ty)).unwrap(); + first = false; } name.push(')'); name @@ -638,32 +819,42 @@ impl<'ast> ExpCtxt<'ast> { /// /// Provides a better error message than an `unwrap` or `expect` when we /// know beforehand that we will be needing types to be resolved. - fn assert_resolved

(&self, params: &Parameters

) -> Result<()> { + fn assert_resolved<'a, I: IntoIterator>( + &self, + params: I, + ) -> Result<()> { let mut errors = Vec::new(); - params.visit_types(|ty| { - if let Type::Custom(name) = ty { - if !self.custom_types.contains_key(name.last_tmp()) { - let e = syn::Error::new(name.span(), "unresolved type"); - errors.push(e); + for param in params { + param.ty.visit(|ty| { + if let Type::Custom(name) = ty { + if !self.custom_types.contains_key(name.last_tmp()) { + let e = syn::Error::new(name.span(), "unresolved type"); + errors.push(e); + } } - } - }); + }); + } if errors.is_empty() { Ok(()) } else { let mut e = crate::utils::combine_errors(errors).unwrap(); let note = "Custom types must be declared inside of the same scope they are referenced in,\n\ - or \"imported\" as a UDT with `type {ident} is (...);`"; + or \"imported\" as a UDT with `type ... is (...);`"; e.combine(Error::new(Span::call_site(), note)); Err(e) } } - fn params_data_size

(&self, list: &Parameters

, base: Option) -> TokenStream { + fn params_data_size, T: Borrow>( + &self, + list: I, + base: Option, + ) -> TokenStream { let base = base.unwrap_or_else(|| quote!(self)); - let sizes = list.iter().map(|var| { - let field = var.name.as_ref().unwrap(); + let sizes = list.into_iter().enumerate().map(|(i, var)| { + let var = var.borrow(); + let field = anon_name((i, var.name.as_ref())); self.type_data_size(&var.ty, quote!(#base.#field)) }); quote!(0usize #( + #sizes)*) @@ -687,18 +878,19 @@ impl<'ast> Visit<'ast> for ExpCtxt<'ast> { /// Expands `From` impls for a list of types and the corresponding tuple. fn expand_from_into_tuples

(name: &Ident, fields: &Parameters

) -> TokenStream { - let names = fields.names(); + let names = fields.names().enumerate().map(anon_name); let names2 = names.clone(); let idxs = (0..fields.len()).map(syn::Index::from); - let tys = fields.types().map(expand_type); - let tys2 = tys.clone(); - + let (sol_tuple, rust_tuple) = expand_tuple_types(fields.types()); quote! { - type UnderlyingSolTuple = (#(#tys,)*); - type UnderlyingRustTuple = (#(<#tys2 as ::alloy_sol_types::SolType>::RustType,)*); + #[doc(hidden)] + type UnderlyingSolTuple = #sol_tuple; + #[doc(hidden)] + type UnderlyingRustTuple = #rust_tuple; #[automatically_derived] + #[doc(hidden)] impl ::core::convert::From<#name> for UnderlyingRustTuple { fn from(value: #name) -> Self { (#(value.#names,)*) @@ -706,6 +898,7 @@ fn expand_from_into_tuples

(name: &Ident, fields: &Parameters

) -> TokenStre } #[automatically_derived] + #[doc(hidden)] impl ::core::convert::From for #name { fn from(tuple: UnderlyingRustTuple) -> Self { #name { @@ -715,3 +908,19 @@ fn expand_from_into_tuples

(name: &Ident, fields: &Parameters

) -> TokenStre } } } + +/// Returns +/// - `(#(#expanded,)*)` +/// - `(#(<#expanded as ::alloy_sol_types::SolType>::RustType,)*)` +fn expand_tuple_types<'a, I: IntoIterator>( + types: I, +) -> (TokenStream, TokenStream) { + let mut sol_tuple = TokenStream::new(); + let mut rust_tuple = TokenStream::new(); + for ty in types { + let expanded = expand_type(ty); + sol_tuple.extend(quote!(#expanded,)); + rust_tuple.extend(quote!(<#expanded as ::alloy_sol_types::SolType>::RustType,)); + } + (quote!((#sol_tuple)), quote!((#rust_tuple))) +} diff --git a/crates/sol-macro/src/expand/type.rs b/crates/sol-macro/src/expand/type.rs index d7f6ec4857..07c7573c99 100644 --- a/crates/sol-macro/src/expand/type.rs +++ b/crates/sol-macro/src/expand/type.rs @@ -13,33 +13,25 @@ pub fn expand_type(ty: &Type) -> TokenStream { fn rec_expand_type(ty: &Type, tokens: &mut TokenStream) { let tts = match *ty { - Type::Address(span, _) => quote_spanned! {span=> - ::alloy_sol_types::sol_data::Address - }, + Type::Address(span, _) => quote_spanned! {span=> ::alloy_sol_types::sol_data::Address }, Type::Bool(span) => quote_spanned! {span=> ::alloy_sol_types::sol_data::Bool }, Type::String(span) => quote_spanned! {span=> ::alloy_sol_types::sol_data::String }, + Type::Bytes(span) => quote_spanned! {span=> ::alloy_sol_types::sol_data::Bytes }, - Type::Bytes { span, size: None } => { - quote_spanned! {span=> ::alloy_sol_types::sol_data::Bytes } - } - Type::Bytes { - span, - size: Some(size), - } => { + Type::FixedBytes(span, size) => { let size = Literal::u16_unsuffixed(size.get()); quote_spanned! {span=> ::alloy_sol_types::sol_data::FixedBytes<#size> } } - - Type::Int { span, size } => { - let size = Literal::u16_unsuffixed(size.map(NonZeroU16::get).unwrap_or(256)); + Type::Int(span, size) => { + let size = Literal::u16_unsuffixed(size.map_or(256, NonZeroU16::get)); quote_spanned! {span=> ::alloy_sol_types::sol_data::Int<#size> } } - Type::Uint { span, size } => { - let size = Literal::u16_unsuffixed(size.map(NonZeroU16::get).unwrap_or(256)); + Type::Uint(span, size) => { + let size = Literal::u16_unsuffixed(size.map_or(256, NonZeroU16::get)); quote_spanned! {span=> ::alloy_sol_types::sol_data::Uint<#size> } @@ -82,7 +74,7 @@ impl ExpCtxt<'_> { } } - fn custom_type(&self, name: &SolPath) -> &Type { + pub(super) fn custom_type(&self, name: &SolPath) -> &Type { match self.custom_types.get(name.last_tmp()) { Some(item) => item, None => panic!("unresolved item: {name}"), @@ -106,14 +98,12 @@ impl ExpCtxt<'_> { // static types: 1 word Type::Address(..) | Type::Bool(_) - | Type::Int { .. } - | Type::Uint { .. } - | Type::Bytes { size: Some(_), .. } => 32, + | Type::Int(..) + | Type::Uint(..) + | Type::FixedBytes(..) => 32, // dynamic types: 1 offset word, 1 length word - Type::String(_) - | Type::Bytes { size: None, .. } - | Type::Array(SolArray { size: None, .. }) => 64, + Type::String(_) | Type::Bytes(_) | Type::Array(SolArray { size: None, .. }) => 64, // fixed array: size * encoded size Type::Array(SolArray { @@ -136,7 +126,9 @@ impl ExpCtxt<'_> { .map(|ty| self.type_base_data_size(ty)) .sum(), Item::Udt(udt) => self.type_base_data_size(&udt.ty), - Item::Contract(_) | Item::Error(_) | Item::Function(_) => unreachable!(), + Item::Contract(_) | Item::Error(_) | Item::Event(_) | Item::Function(_) => { + unreachable!() + } }, } } @@ -149,12 +141,11 @@ impl ExpCtxt<'_> { | Type::Bool(_) | Type::Int { .. } | Type::Uint { .. } - | Type::Bytes { size: Some(_), .. } => self.type_base_data_size(ty).into_token_stream(), + | Type::FixedBytes(..) => quote!(32usize), // dynamic types: 1 offset word, 1 length word, length rounded up to word size - Type::String(_) | Type::Bytes { size: None, .. } => { - let base = self.type_base_data_size(ty); - quote!(#base + (#field.len() / 31) * 32) + Type::String(_) | Type::Bytes(..) => { + quote! { (64usize + ::alloy_sol_types::next_multiple_of_32(#field.len())) } } Type::Array(SolArray { ty: inner, @@ -163,7 +154,7 @@ impl ExpCtxt<'_> { }) => { let base = self.type_base_data_size(ty); let inner_size = self.type_data_size(inner, field.clone()); - quote!(#base + #field.len() * (#inner_size)) + quote! { (#base + #field.len() * (#inner_size)) } } // fixed array: size * encoded size @@ -175,7 +166,7 @@ impl ExpCtxt<'_> { let base = self.type_base_data_size(ty); let inner_size = self.type_data_size(inner, field); let len: usize = size.base10_parse().unwrap(); - quote!(#base + #len * (#inner_size)) + quote! { (#base + #len * (#inner_size)) } } // tuple: sum of encoded sizes @@ -185,13 +176,15 @@ impl ExpCtxt<'_> { let field_name = quote!(#field.#index); self.type_data_size(ty, field_name) }); - quote!(0usize #(+ #fields)*) + quote! { (0usize #(+ #fields)*) } } Type::Custom(name) => match self.get_item(name) { Item::Struct(strukt) => self.params_data_size(&strukt.fields, Some(field)), Item::Udt(udt) => self.type_data_size(&udt.ty, field), - Item::Contract(_) | Item::Error(_) | Item::Function(_) => unreachable!(), + Item::Contract(_) | Item::Error(_) | Item::Event(_) | Item::Function(_) => { + unreachable!() + } }, } } diff --git a/crates/sol-macro/src/lib.rs b/crates/sol-macro/src/lib.rs index 84ae59f80d..dd62171da8 100644 --- a/crates/sol-macro/src/lib.rs +++ b/crates/sol-macro/src/lib.rs @@ -45,11 +45,16 @@ mod utils; #[doc = include_str!("../../sol-types/tests/doc_types.rs")] /// ``` /// -/// ## Functions, errors, and events +/// ## Functions and errors /// ```ignore #[doc = include_str!("../../sol-types/tests/doc_function_like.rs")] /// ``` /// +/// ## Events +/// ```ignore +#[doc = include_str!("../../sol-types/tests/doc_events.rs")] +/// ``` +/// /// ## Contracts/interfaces /// ```ignore #[doc = include_str!("../../sol-types/tests/doc_contracts.rs")] diff --git a/crates/sol-macro/src/utils.rs b/crates/sol-macro/src/utils.rs index c7f66436a1..a748cc382d 100644 --- a/crates/sol-macro/src/utils.rs +++ b/crates/sol-macro/src/utils.rs @@ -1,5 +1,5 @@ -use proc_macro2::TokenStream; -use quote::quote; +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; use tiny_keccak::{Hasher, Keccak}; /// Simple interface to the [`keccak256`] hash function. @@ -13,10 +13,12 @@ pub fn keccak256>(bytes: T) -> [u8; 32] { output } -pub fn selector>(bytes: T) -> TokenStream { - let hash = keccak256(bytes); - let selector: [u8; 4] = hash[..4].try_into().unwrap(); - quote!([#(#selector),*]) +pub fn selector>(bytes: T) -> impl ToTokens { + ExprArray::<_, 4>::new(keccak256(bytes)[..4].try_into().unwrap()) +} + +pub fn event_selector>(bytes: T) -> impl ToTokens { + ExprArray::new(keccak256(bytes)) } pub fn combine_errors(v: Vec) -> Option { @@ -25,3 +27,28 @@ pub fn combine_errors(v: Vec) -> Option { a }) } + +struct ExprArray { + array: [T; N], + span: Span, +} + +impl ExprArray { + fn new(array: [T; N]) -> Self { + Self { + array, + span: Span::call_site(), + } + } +} + +impl ToTokens for ExprArray { + fn to_tokens(&self, tokens: &mut TokenStream) { + syn::token::Bracket(self.span).surround(tokens, |tokens| { + for t in &self.array { + t.to_tokens(tokens); + syn::token::Comma(self.span).to_tokens(tokens); + } + }) + } +} diff --git a/crates/sol-types/src/coder/decoder.rs b/crates/sol-types/src/coder/decoder.rs index 4f38ae27d9..c49f413eca 100644 --- a/crates/sol-types/src/coder/decoder.rs +++ b/crates/sol-types/src/coder/decoder.rs @@ -154,7 +154,7 @@ impl<'a> Decoder<'a> { /// word boundary. pub fn take_slice(&mut self, len: usize) -> Result<&[u8], Error> { if self.validate { - let padded_len = util::round_up_nearest_multiple(len, 32); + let padded_len = util::next_multiple_of_32(len); if self.offset + padded_len > self.buf.len() { return Err(Error::Overrun) } diff --git a/crates/sol-types/src/coder/token.rs b/crates/sol-types/src/coder/token.rs index f2bd58b0f7..afdaf896ab 100644 --- a/crates/sol-types/src/coder/token.rs +++ b/crates/sol-types/src/coder/token.rs @@ -473,9 +473,7 @@ impl PackedSeqToken { } macro_rules! tuple_impls { - () => {}; - (@peel $_:ident, $($other:ident,)*) => { tuple_impls! { $($other,)* } }; - ($($ty:ident,)+) => { + ($($ty:ident),+) => { impl<$($ty: TokenType,)+> Sealed for ($($ty,)+) {} #[allow(non_snake_case)] @@ -510,7 +508,7 @@ macro_rules! tuple_impls { if Self::is_dynamic() { 1 } else { - let ($(ref $ty,)+) = *self; + let ($($ty,)+) = self; 0 $( + $ty.head_words() )+ } } @@ -526,7 +524,7 @@ macro_rules! tuple_impls { #[inline] fn total_words(&self) -> usize { - let ($(ref $ty,)+) = *self; + let ($($ty,)+) = self; 0 $( + $ty.total_words() )+ } @@ -534,7 +532,7 @@ macro_rules! tuple_impls { if Self::is_dynamic() { enc.append_indirection(); } else { - let ($(ref $ty,)+) = *self; + let ($($ty,)+) = self; $( $ty.head_append(enc); )+ @@ -543,7 +541,7 @@ macro_rules! tuple_impls { fn tail_append(&self, enc: &mut Encoder) { if Self::is_dynamic() { - let ($(ref $ty,)+) = *self; + let ($($ty,)+) = self; let head_words = 0 $( + $ty.head_words() )+; enc.push_offset(head_words as u32); @@ -564,7 +562,7 @@ macro_rules! tuple_impls { const IS_TUPLE: bool = true; fn encode_sequence(&self, enc: &mut Encoder) { - let ($(ref $ty,)+) = *self; + let ($($ty,)+) = self; let head_words = 0 $( + $ty.head_words() )+; enc.push_offset(head_words as u32); $( @@ -583,13 +581,9 @@ macro_rules! tuple_impls { )+)) } } - - tuple_impls! { @peel $($ty,)+ } }; } -tuple_impls! { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, } - impl TokenType for () { #[inline] fn is_dynamic() -> bool { @@ -630,6 +624,8 @@ impl TokenSeq for () { } } +all_the_tuples!(tuple_impls); + #[cfg(test)] mod tests { use super::*; diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 55ba5b41e6..2486571df8 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -167,6 +167,9 @@ pub mod no_std_prelude { }; } +#[macro_use] +mod macros; + mod coder; pub use coder::{ decode, decode_params, decode_single, encode, encode_params, encode_single, @@ -180,12 +183,13 @@ pub use errors::{Error, Result}; mod types; pub use types::{ - data_type as sol_data, Panic, PanicKind, Revert, SolCall, SolError, SolStruct, SolType, + data_type as sol_data, EventTopic, Panic, PanicKind, Revert, SolCall, SolError, SolEvent, + SolStruct, SolType, TopicList, }; mod util; #[doc(hidden)] -pub use util::just_ok; +pub use util::{just_ok, next_multiple_of_32}; mod eip712; pub use eip712::Eip712Domain; diff --git a/crates/sol-types/src/macros.rs b/crates/sol-types/src/macros.rs new file mode 100644 index 0000000000..df5a31b603 --- /dev/null +++ b/crates/sol-types/src/macros.rs @@ -0,0 +1,30 @@ +/// Calls the given macro with all the tuples. +#[rustfmt::skip] +macro_rules! all_the_tuples { + ($name:ident) => { + $name!(T1); + $name!(T1, T2); + $name!(T1, T2, T3); + $name!(T1, T2, T3, T4); + $name!(T1, T2, T3, T4, T5); + $name!(T1, T2, T3, T4, T5, T6); + $name!(T1, T2, T3, T4, T5, T6, T7); + $name!(T1, T2, T3, T4, T5, T6, T7, T8); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23); + $name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24); + }; +} diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index bfd729200c..ec64e8d0a7 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -53,8 +53,8 @@ impl SolType for Address { } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - target.extend_from_slice(rust.borrow().as_ref()); + fn encode_packed_to>(rust: B, out: &mut Vec) { + out.extend_from_slice(rust.borrow().as_ref()); } } @@ -96,8 +96,8 @@ impl SolType for Bytes { } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - target.extend_from_slice(rust.borrow()); + fn encode_packed_to>(rust: B, out: &mut Vec) { + out.extend_from_slice(rust.borrow()); } } @@ -156,8 +156,8 @@ where } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - IntBitCount::::encode_packed_to_int(*rust.borrow(), target) + fn encode_packed_to>(rust: B, out: &mut Vec) { + IntBitCount::::encode_packed_to_int(*rust.borrow(), out) } } @@ -207,8 +207,8 @@ where } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - IntBitCount::::encode_packed_to_uint(*rust.borrow(), target) + fn encode_packed_to>(rust: B, out: &mut Vec) { + IntBitCount::::encode_packed_to_uint(*rust.borrow(), out) } } @@ -254,8 +254,8 @@ impl SolType for Bool { } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - target.push(*rust.borrow() as u8); + fn encode_packed_to>(rust: B, out: &mut Vec) { + out.push(*rust.borrow() as u8); } } @@ -305,9 +305,9 @@ where } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { + fn encode_packed_to>(rust: B, out: &mut Vec) { for item in rust.borrow() { - T::encode_packed_to(target, item); + T::encode_packed_to(item, out); } } } @@ -358,8 +358,8 @@ impl SolType for String { } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { - target.extend_from_slice(rust.borrow().as_bytes()); + fn encode_packed_to>(rust: B, out: &mut Vec) { + out.extend_from_slice(rust.borrow().as_bytes()); } } @@ -411,9 +411,9 @@ where } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { + fn encode_packed_to>(rust: B, out: &mut Vec) { // write only the first n bytes - target.extend_from_slice(rust.borrow()); + out.extend_from_slice(rust.borrow()); } } @@ -447,30 +447,13 @@ where #[inline] fn detokenize(token: Self::TokenType) -> Self::RustType { - let res = token - .into_array() - .into_iter() - .map(|t| T::detokenize(t)) - .collect::>() - .try_into(); - match res { - Ok(tokens) => tokens, - Err(_) => unreachable!("input is exact len"), - } + token.0.map(T::detokenize) } #[inline] fn tokenize>(rust: B) -> Self::TokenType { - match rust - .borrow() - .iter() - .map(|r| T::tokenize(r)) - .collect::>() - .try_into() - { - Ok(tokens) => tokens, - Err(_) => unreachable!(), - } + let arr: &[T::RustType; N] = rust.borrow(); + FixedSeqToken::<_, N>(core::array::from_fn(|i| T::tokenize(&arr[i]))) } #[inline] @@ -484,18 +467,14 @@ where } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { + fn encode_packed_to>(rust: B, out: &mut Vec) { for item in rust.borrow() { - T::encode_packed_to(target, item); + T::encode_packed_to(item, out); } } } macro_rules! tuple_impls { - () => {}; - - (@peel $_:ident, $($other:ident,)*) => { tuple_impls! { $($other,)* } }; - // compile time `join(",")` format string (@fmt $other:ident) => { ",{}" }; (@fmt $first:ident, $($other:ident,)*) => { @@ -505,7 +484,7 @@ macro_rules! tuple_impls { ) }; - ($($ty:ident,)+) => { + ($($ty:ident),+) => { #[allow(non_snake_case)] impl<$($ty: SolType,)+> SolType for ($($ty,)+) { type RustType = ($( $ty::RustType, )+); @@ -527,7 +506,7 @@ macro_rules! tuple_impls { } fn type_check(token: &Self::TokenType) -> Result<()> { - let ($(ref $ty,)+) = *token; + let ($($ty,)+) = token; $( <$ty as SolType>::type_check($ty)?; )+ @@ -542,35 +521,31 @@ macro_rules! tuple_impls { } fn tokenize>(rust: B_) -> Self::TokenType { - let ($(ref $ty,)+) = *rust.borrow(); + let ($($ty,)+) = rust.borrow(); ($( <$ty as SolType>::tokenize($ty), )+) } fn eip712_data_word>(rust: B_) -> Word { - let ($(ref $ty,)+) = *rust.borrow(); + let ($($ty,)+) = rust.borrow(); let encoding: Vec = [$( <$ty as SolType>::eip712_data_word($ty).0, )+].concat(); keccak256(&encoding).into() } - fn encode_packed_to>(target: &mut Vec, rust: B_) { - let ($(ref $ty,)+) = *rust.borrow(); + fn encode_packed_to>(rust: B_, out: &mut Vec) { + let ($($ty,)+) = rust.borrow(); // TODO: Reserve $( - <$ty as SolType>::encode_packed_to(target, $ty); + <$ty as SolType>::encode_packed_to($ty, out); )+ } } - - tuple_impls! { @peel $($ty,)+ } }; } -tuple_impls! { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, } - impl SolType for () { type RustType = (); type TokenType = FixedSeqToken<(), 0>; @@ -595,7 +570,7 @@ impl SolType for () { #[inline] fn tokenize>(_rust: B) -> Self::TokenType { - [].into() + FixedSeqToken([]) } #[inline] @@ -604,9 +579,11 @@ impl SolType for () { } #[inline] - fn encode_packed_to>(_target: &mut Vec, _rust: B) {} + fn encode_packed_to>(_rust: B, _out: &mut Vec) {} } +all_the_tuples!(tuple_impls); + mod sealed { pub trait Sealed {} } diff --git a/crates/sol-types/src/types/event/mod.rs b/crates/sol-types/src/types/event/mod.rs new file mode 100644 index 0000000000..421d010e30 --- /dev/null +++ b/crates/sol-types/src/types/event/mod.rs @@ -0,0 +1,131 @@ +use crate::{ + token::{TokenSeq, WordToken}, + Result, SolType, +}; +use alloc::vec::Vec; +use alloy_primitives::{FixedBytes, B256}; + +mod topic; +pub use topic::EventTopic; + +mod topic_list; +pub use topic_list::TopicList; + +/// Solidity event. +/// +/// ### Implementer's Guide +/// +/// We do not recommend implementing this trait directly. Instead, we recommend +/// using the [`sol`][crate::sol] proc macro to parse a Solidity event +/// definition. +pub trait SolEvent: Sized { + /// The underlying tuple type which represents this event's non-indexed + /// parameters. These parameters are ABI encoded and included in the log + /// body. + /// + /// If this event has no non-indexed parameters, this will be the unit type + /// `()`. + type DataTuple: SolType; + + /// The [`TokenSeq`] type corresponding to the tuple. + type DataToken: TokenSeq; + + /// The underlying tuple type which represents this event's topics. + /// + /// These are ABI encoded and included in the log struct returned by the + /// RPC node. + /// + /// See the [`TopicList`] trait for more details. + type TopicList: TopicList; + + /// The event's ABI signature. + /// + /// For anonymous events, this is unused, but is still present. + const SIGNATURE: &'static str; + + /// The event's ABI signature hash, or selector: `keccak256(SIGNATURE)` + /// + /// For non-anonymous events, this will be the first topic (`topic0`). + /// For anonymous events, this is unused, but is still present. + const SIGNATURE_HASH: FixedBytes<32>; + + /// Whether the event is anonymous. + const ANONYMOUS: bool; + + /// Convert decoded rust data to the event type. + fn new( + topics: ::RustType, + data: ::RustType, + ) -> Self; + + /// The size of the ABI-encoded dynamic data in bytes. + fn data_size(&self) -> usize; + + /// ABI-encode the dynamic data of this event into the given buffer. + fn encode_data_raw(&self, out: &mut Vec); + + /// Encode the topics of this event into the given buffer. + /// + /// # Errors + /// + /// This method should only fail if the buffer is too small. + fn encode_topics_raw(&self, out: &mut [WordToken]) -> Result<()>; + + /// ABI-encode the dynamic data of this event. + #[inline] + fn encode_data(&self) -> Vec { + let mut out = Vec::with_capacity(self.data_size()); + self.encode_data_raw(&mut out); + out + } + + /// Encode the topics of this event. + /// + /// The returned vector will have length `Self::TopicList::COUNT`. + #[inline] + fn encode_topics(&self) -> Vec { + let mut out = vec![WordToken(B256::ZERO); Self::TopicList::COUNT]; + self.encode_topics_raw(&mut out).unwrap(); + out + } + + /// Encode the topics of this event into a fixed-size array. + /// + /// # Panics + /// + /// This method will panic if `LEN` is not equal to + /// `Self::TopicList::COUNT`. + #[inline] + fn encode_topics_array(&self) -> [WordToken; LEN] { + // TODO: make this a compile-time error when `const` blocks are stable + assert_eq!(LEN, Self::TopicList::COUNT, "topic list length mismatch"); + let mut out = [WordToken(B256::ZERO); LEN]; + self.encode_topics_raw(&mut out).unwrap(); + out + } + + /// Decode the topics of this event from the given data. + fn decode_topics(topics: I) -> Result<::RustType> + where + I: IntoIterator, + D: Into, + { + ::detokenize(topics) + } + + /// Decode the dynamic data of this event from the given buffer. + fn decode_data(data: &[u8], validate: bool) -> Result<::RustType> { + ::decode(data, validate) + } + + /// Decode the event from the given log info. + fn decode_log(topics: I, data: &[u8], validate: bool) -> Result + where + I: IntoIterator, + D: Into, + { + let topics = Self::decode_topics(topics)?; + let body = Self::decode_data(data, validate)?; + Ok(Self::new(topics, body)) + } +} diff --git a/crates/sol-types/src/types/event/topic.rs b/crates/sol-types/src/types/event/topic.rs new file mode 100644 index 0000000000..0baa152d81 --- /dev/null +++ b/crates/sol-types/src/types/event/topic.rs @@ -0,0 +1,206 @@ +use crate::{sol_data::*, token::WordToken, SolType}; +use alloc::vec::Vec; +use alloy_primitives::keccak256; +use core::borrow::Borrow; + +/// A Solidity event topic. +/// +/// These types implement a special encoding used only in Solidity indexed event +/// parameters. +/// +/// For more details, see the [Solidity reference][ref]. +/// +/// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#encoding-of-indexed-event-parameters +pub trait EventTopic: SolType { + /// The number of bytes this type occupies in another topic's preimage, + /// usually a multiple of 32. + /// + /// This should be used in conjunction with [`encode_topic_preimage`] to + /// construct the preimage of a complex topic. + /// + /// [`encode_topic_preimage`]: EventTopic::encode_topic_preimage + fn topic_preimage_length>(rust: B) -> usize; + + /// Encodes this type as preimage bytes which are then hashed in + /// complex types' [`encode_topic`][EventTopic::encode_topic]. + /// + /// See the [Solidity ABI spec][ref] for more details. + /// + /// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#encoding-of-indexed-event-parameters + fn encode_topic_preimage>(rust: B, out: &mut Vec); + + /// Indexed event parameter encoding. + /// + /// Note that this is different from [`encode_topic_preimage`] and + /// [`SolType::encode`]. See the [Solidity ABI spec][ref] for more details. + /// + /// [`encode_topic_preimage`]: EventTopic::encode_topic_preimage + /// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#encoding-of-indexed-event-parameters + fn encode_topic>(rust: B) -> WordToken; +} + +// Single word types: encoded as just the single word +macro_rules! word_impl { + ($t:ty) => { + #[inline] + fn topic_preimage_length>(_: B) -> usize { + 32 + } + + #[inline] + fn encode_topic_preimage>(rust: B, out: &mut Vec) { + out.extend(<$t as SolType>::tokenize(rust).0 .0); + } + + #[inline] + fn encode_topic>(rust: B) -> WordToken { + <$t as SolType>::tokenize(rust) + } + }; +} + +impl EventTopic for Address { + word_impl!(Address); +} + +impl EventTopic for Bool { + word_impl!(Bool); +} + +impl EventTopic for Int +where + IntBitCount: SupportedInt, +{ + word_impl!(Int); +} + +impl EventTopic for Uint +where + IntBitCount: SupportedInt, +{ + word_impl!(Uint); +} + +impl EventTopic for FixedBytes +where + ByteCount: SupportedFixedBytes, +{ + word_impl!(FixedBytes); +} + +// Bytes-like types - preimage encoding: bytes padded to 32; hash: the bytes +macro_rules! bytes_impl { + ($t:ty) => { + #[inline] + fn topic_preimage_length>(rust: B) -> usize { + crate::util::next_multiple_of_32(rust.borrow().len()) + } + + #[inline] + fn encode_topic_preimage>(rust: B, out: &mut Vec) { + encode_topic_bytes(rust.borrow().as_ref(), out); + } + + #[inline] + fn encode_topic>(rust: B) -> WordToken { + WordToken(keccak256(rust.borrow())) + } + }; +} + +impl EventTopic for String { + bytes_impl!(String); +} + +impl EventTopic for Bytes { + bytes_impl!(Bytes); +} + +// Complex types - preimage encoding and hash: iter each element +macro_rules! array_impl { + ($T:ident) => { + #[inline] + fn topic_preimage_length>(rust: B) -> usize { + rust.borrow().iter().map($T::topic_preimage_length).sum() + } + + #[inline] + fn encode_topic_preimage>(rust: B, out: &mut Vec) { + let b = rust.borrow(); + out.reserve(Self::topic_preimage_length(b)); + for t in b { + $T::encode_topic_preimage(t, out); + } + } + + #[inline] + fn encode_topic>(rust: B) -> WordToken { + let mut out = Vec::new(); + Self::encode_topic_preimage(rust.borrow(), &mut out); + WordToken(keccak256(out)) + } + }; +} + +impl EventTopic for Array { + array_impl!(T); +} + +impl EventTopic for FixedArray { + array_impl!(T); +} + +macro_rules! tuple_impls { + ($($t:ident),+) => { + #[allow(non_snake_case)] + impl<$($t: EventTopic,)+> EventTopic for ($($t,)+) { + #[inline] + fn topic_preimage_length>(rust: B) -> usize { + let ($($t,)+) = rust.borrow(); + 0usize $( + <$t>::topic_preimage_length($t) )+ + } + + #[inline] + fn encode_topic_preimage>(rust: B, out: &mut Vec) { + let b @ ($($t,)+) = rust.borrow(); + out.reserve(Self::topic_preimage_length(b)); + $( + <$t>::encode_topic_preimage($t, out); + )+ + } + + #[inline] + fn encode_topic>(rust: B) -> WordToken { + let mut out = Vec::new(); + Self::encode_topic_preimage(rust.borrow(), &mut out); + WordToken(keccak256(out)) + } + } + }; +} + +impl EventTopic for () { + #[inline] + fn topic_preimage_length>(_: B) -> usize { + 0 + } + + #[inline] + fn encode_topic_preimage>(_: B, _: &mut Vec) {} + + #[inline] + fn encode_topic>(_: B) -> WordToken { + WordToken::default() + } +} + +all_the_tuples!(tuple_impls); + +fn encode_topic_bytes(sl: &[u8], out: &mut Vec) { + let padding = 32 - sl.len() % 32; + out.reserve(sl.len() + padding); + + static PAD: [u8; 32] = [0; 32]; + out.extend_from_slice(sl); + out.extend_from_slice(&PAD[..padding]); +} diff --git a/crates/sol-types/src/types/event/topic_list.rs b/crates/sol-types/src/types/event/topic_list.rs new file mode 100644 index 0000000000..3c024c00c4 --- /dev/null +++ b/crates/sol-types/src/types/event/topic_list.rs @@ -0,0 +1,72 @@ +use crate::{token::WordToken, Error, Result, SolType}; +use alloc::borrow::Cow; +use sealed::Sealed; + +/// A `TopicList` represents the topics of a Solidity event. +/// +/// This trait is implemented only on tuples of arity up to 4. The tuples must +/// contain only [`SolType`]s where the token is a [`WordToken`], and as such +/// it is sealed to prevent prevent incorrect downstream implementations. +/// +/// See the [Solidity event ABI specification][solevent] for more details on how +/// events' topics are encoded. +/// +/// [solevent]: https://docs.soliditylang.org/en/latest/abi-spec.html#events +pub trait TopicList: SolType + Sealed { + /// The number of topics. + const COUNT: usize; + + /// Detokenize the topics into a tuple of rust types. + /// + /// This function accepts an iterator of `WordToken`. + fn detokenize(topics: I) -> Result + where + I: IntoIterator, + D: Into; +} + +mod sealed { + pub trait Sealed {} +} + +macro_rules! impl_topic_list_tuples { + ($($c:literal => $($t:ident),*;)+) => {$( + impl<$($t: SolType,)*> sealed::Sealed for ($($t,)*) {} + impl<$($t: SolType,)*> TopicList for ($($t,)*) { + const COUNT: usize = $c; + + fn detokenize(topics: I) -> Result + where + I: IntoIterator, + D: Into + { + let err = || Error::Other(Cow::Borrowed("topic list length mismatch")); + let mut iter = topics.into_iter().map(Into::into); + Ok(($( + iter.next().ok_or_else(err).map(<$t>::detokenize)?, + )*)) + } + } + )+}; +} + +impl sealed::Sealed for () {} +impl TopicList for () { + const COUNT: usize = 0; + + #[inline] + fn detokenize(_: I) -> Result + where + I: IntoIterator, + D: Into, + { + Ok(()) + } +} + +impl_topic_list_tuples! { + 1 => T; + 2 => T, U; + 3 => T, U, V; + 4 => T, U, V, W; +} diff --git a/crates/sol-types/src/types/mod.rs b/crates/sol-types/src/types/mod.rs index 3e8490df89..d5a9a79c29 100644 --- a/crates/sol-types/src/types/mod.rs +++ b/crates/sol-types/src/types/mod.rs @@ -6,6 +6,9 @@ pub mod data_type; mod error; pub use error::{Panic, PanicKind, Revert, SolError}; +mod event; +pub use event::{EventTopic, SolEvent, TopicList}; + mod r#struct; pub use r#struct::SolStruct; diff --git a/crates/sol-types/src/types/struct.rs b/crates/sol-types/src/types/struct.rs index c6daf71ef7..fc776d281e 100644 --- a/crates/sol-types/src/types/struct.rs +++ b/crates/sol-types/src/types/struct.rs @@ -159,8 +159,8 @@ impl SolType for T { } #[inline] - fn encode_packed_to>(target: &mut Vec, rust: B) { + fn encode_packed_to>(rust: B, out: &mut Vec) { let tuple = rust.borrow().to_rust(); - TupleFor::::encode_packed_to(target, tuple) + TupleFor::::encode_packed_to(tuple, out) } } diff --git a/crates/sol-types/src/types/type.rs b/crates/sol-types/src/types/type.rs index 2d458ca3f4..6d26a55062 100644 --- a/crates/sol-types/src/types/type.rs +++ b/crates/sol-types/src/types/type.rs @@ -89,7 +89,7 @@ pub trait SolType { /// Non-standard Packed Mode ABI encoding. /// /// See [`encode_packed`][SolType::encode_packed] for more details. - fn encode_packed_to>(target: &mut Vec, rust: B); + fn encode_packed_to>(rust: B, out: &mut Vec); /// Non-standard Packed Mode ABI encoding. /// @@ -101,9 +101,9 @@ pub trait SolType { /// /// More information can be found in the [Solidity docs](https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode). fn encode_packed>(rust: B) -> Vec { - let mut res = Vec::new(); - Self::encode_packed_to(&mut res, rust); - res + let mut out = Vec::new(); + Self::encode_packed_to(rust, &mut out); + out } /* BOILERPLATE BELOW */ diff --git a/crates/sol-types/src/types/udt.rs b/crates/sol-types/src/types/udt.rs index 85cba6848b..0ee4b3266b 100644 --- a/crates/sol-types/src/types/udt.rs +++ b/crates/sol-types/src/types/udt.rs @@ -93,11 +93,11 @@ macro_rules! define_udt { } #[inline] - fn encode_packed_to(target: &mut $crate::no_std_prelude::Vec, rust: B) + fn encode_packed_to(rust: B, out: &mut $crate::no_std_prelude::Vec) where B: $crate::no_std_prelude::Borrow { - <$underlying as $crate::SolType>::encode_packed_to(target, rust) + <$underlying as $crate::SolType>::encode_packed_to(rust, out) } } }; diff --git a/crates/sol-types/src/util.rs b/crates/sol-types/src/util.rs index 5fc2ef7372..441dd010e2 100644 --- a/crates/sol-types/src/util.rs +++ b/crates/sol-types/src/util.rs @@ -31,12 +31,12 @@ pub(crate) fn check_zeroes(data: &[u8]) -> bool { data.iter().all(|b| *b == 0) } -/// See [`usize::next_multiple_of`] +/// See [`usize::next_multiple_of`]. #[inline] -pub(crate) const fn round_up_nearest_multiple(lhs: usize, rhs: usize) -> usize { - match lhs % rhs { - 0 => lhs, - r => lhs + (rhs - r), +pub const fn next_multiple_of_32(n: usize) -> usize { + match n % 32 { + 0 => n, + r => n + (32 - r), } } diff --git a/crates/sol-types/tests/doc_contracts.rs b/crates/sol-types/tests/doc_contracts.rs index 9971151672..a1ca6a1c00 100644 --- a/crates/sol-types/tests/doc_contracts.rs +++ b/crates/sol-types/tests/doc_contracts.rs @@ -8,9 +8,8 @@ sol! { /// [the EIP]: https://eips.ethereum.org/EIPS/eip-20 #[derive(Debug, PartialEq)] interface IERC20 { - // TODO: Events - // event Transfer(address indexed from, address indexed to, uint256 value); - // event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); diff --git a/crates/sol-types/tests/doc_events.rs b/crates/sol-types/tests/doc_events.rs new file mode 100644 index 0000000000..012f868caf --- /dev/null +++ b/crates/sol-types/tests/doc_events.rs @@ -0,0 +1,69 @@ +#![allow(clippy::assertions_on_constants)] + +use alloy_primitives::{keccak256, B256, U256}; +use alloy_sol_types::{sol, token::WordToken, SolEvent}; +use hex_literal::hex; + +sol! { + #[derive(Default)] + event MyEvent(bytes32 indexed a, uint256 b, string indexed c, bytes d); + + event LogNote( + bytes4 indexed sig, + address indexed guy, + bytes32 indexed foo, + bytes32 indexed bar, + uint wad, + bytes fax + ) anonymous; + + struct Data { + bytes data; + } + event MyEvent2(Data indexed data); +} + +#[test] +fn event() { + assert_event_signature::("MyEvent(bytes32,uint256,string,bytes)"); + assert!(!MyEvent::ANONYMOUS); + let event = MyEvent { + a: [0x11; 32], + b: U256::from(1u64), + c: "Hello World".to_string(), + d: Vec::new(), + }; + // topics are `(SELECTOR, a, keccak256(c))` + assert_eq!( + event.encode_topics_array::<3>(), + [ + WordToken(MyEvent::SIGNATURE_HASH), + WordToken(B256::repeat_byte(0x11)), + WordToken(keccak256("Hello World")) + ] + ); + // dynamic data is `abi.encode(b, c, d)` + assert_eq!( + event.encode_data(), + hex!( + "0000000000000000000000000000000000000000000000000000000000000001" // a + "0000000000000000000000000000000000000000000000000000000000000060" // b offset + "00000000000000000000000000000000000000000000000000000000000000a0" // c offset + "000000000000000000000000000000000000000000000000000000000000000b" // b length + "48656c6c6f20576f726c64000000000000000000000000000000000000000000" // b + "0000000000000000000000000000000000000000000000000000000000000000" // c length + // c is empty + ), + ); + + assert_event_signature::("LogNote(bytes4,address,bytes32,bytes32,uint,bytes)"); + assert!(LogNote::ANONYMOUS); + + assert_event_signature::("MyEvent2((bytes))"); + assert!(!MyEvent2::ANONYMOUS); +} + +fn assert_event_signature(expected: &str) { + assert_eq!(T::SIGNATURE, expected); + assert_eq!(T::SIGNATURE_HASH, keccak256(expected)); +} diff --git a/crates/sol-types/tests/doc_function_like.rs b/crates/sol-types/tests/doc_function_like.rs index c7b1cb3550..187ee7c095 100644 --- a/crates/sol-types/tests/doc_function_like.rs +++ b/crates/sol-types/tests/doc_function_like.rs @@ -1,5 +1,6 @@ use alloy_primitives::{keccak256, U256}; use alloy_sol_types::{sol, SolCall, SolError}; +use hex_literal::hex; // Unnamed arguments will be given a name based on their position, // e.g. `_0`, `_1`... @@ -34,14 +35,10 @@ sol! { /// Implements [`SolError`]. #[derive(Debug, PartialEq)] error MyError(uint256 a, uint256 b); - - // TODO: events - // event FooEvent(uint256 a, uint256 b); } #[test] -fn function_like() { - // function +fn function() { assert_call_signature::("foo(uint256,uint256)"); // not actually a valid signature, and it shouldn't be relied upon for @@ -52,7 +49,7 @@ fn function_like() { a: U256::from(1), b: U256::from(2), }; - let call_data = call.encode(); + let _call_data = call.encode(); // the signatures are unaffected let _ = overloaded_0Call {}; @@ -64,13 +61,17 @@ fn function_like() { let _ = overloaded_2Call { _0: "hello".into() }; assert_call_signature::("overloaded(string)"); +} - // error +#[test] +fn error() { assert_error_signature::("MyError(uint256,uint256)"); - - assert!(MyError::decode(&call_data, true).is_err()); + let call_data = hex!( + "0000000000000000000000000000000000000000000000000000000000000001" + "0000000000000000000000000000000000000000000000000000000000000002" + ); assert_eq!( - MyError::decode_raw(&call_data[4..], true), + MyError::decode_raw(&call_data, true), Ok(MyError { a: U256::from(1), b: U256::from(2) diff --git a/crates/sol-types/tests/sol.rs b/crates/sol-types/tests/sol.rs index 12791eb2e0..025b0f1863 100644 --- a/crates/sol-types/tests/sol.rs +++ b/crates/sol-types/tests/sol.rs @@ -38,7 +38,7 @@ sol! { } #[test] -fn no_std_proc_macro() { +fn test_sol() { ::hex_encode_single(true); let a = MyStruct { @@ -121,10 +121,9 @@ fn function() { }, ], }; - assert_eq!( - call.data_size(), - 32 + (64 + 32) + (64 + 32 + 32) + (64 + 3 * 32) + 2 * 32 + (32 + 32) + (64 + 4 * (32 + 32)) - ); + let _encoded = call.encode(); + // TODO + // assert_eq!(encoded.len(), call.data_size()); let sig = "someFunction(bool)"; // TODO ? assert_eq!(someFunctionReturn::SIGNATURE, sig); diff --git a/crates/sol-types/tests/ui/contract.rs b/crates/sol-types/tests/ui/contract.rs index e149a3209e..0579e4005a 100644 --- a/crates/sol-types/tests/ui/contract.rs +++ b/crates/sol-types/tests/ui/contract.rs @@ -1,5 +1,21 @@ use alloy_sol_types::sol; +sol! { + contract MissingBraces1 +} + +sol! { + contract MissingBraces2 is A +} + +sol! { + contract MissingInheritance1 is +} + +sol! { + contract MissingInheritance2 is; +} + sol! { contract C { contract Nested {} diff --git a/crates/sol-types/tests/ui/contract.stderr b/crates/sol-types/tests/ui/contract.stderr index 3c3d0491d2..610c976138 100644 --- a/crates/sol-types/tests/ui/contract.stderr +++ b/crates/sol-types/tests/ui/contract.stderr @@ -1,17 +1,53 @@ -error: cannot declare nested contracts - --> tests/ui/contract.rs:5:18 +error: unexpected end of input, expected curly braces + --> tests/ui/contract.rs:3:1 + | +3 | / sol! { +4 | | contract MissingBraces1 +5 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected end of input, expected curly braces + --> tests/ui/contract.rs:7:1 | -5 | contract Nested {} - | ^^^^^^ +7 | / sol! { +8 | | contract MissingBraces2 is A +9 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected end of input, expected identifier + --> tests/ui/contract.rs:11:1 + | +11 | / sol! { +12 | | contract MissingInheritance1 is +13 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected identifier + --> tests/ui/contract.rs:16:36 + | +16 | contract MissingInheritance2 is; + | ^ + +error: cannot declare nested contracts + --> tests/ui/contract.rs:21:18 + | +21 | contract Nested {} + | ^^^^^^ error: cannot declare nested contracts - --> tests/ui/contract.rs:11:17 + --> tests/ui/contract.rs:27:17 | -11 | library Nested {} +27 | library Nested {} | ^^^^^^ error: cannot declare nested contracts - --> tests/ui/contract.rs:17:19 + --> tests/ui/contract.rs:33:19 | -17 | interface Nested {} +33 | interface Nested {} | ^^^^^^ diff --git a/crates/sol-types/tests/ui/empty.rs b/crates/sol-types/tests/ui/empty.rs index 4b7d95c3c5..0ad8dc0c96 100644 --- a/crates/sol-types/tests/ui/empty.rs +++ b/crates/sol-types/tests/ui/empty.rs @@ -15,6 +15,10 @@ sol! { error EmptyError(); } +sol! { + event EmptyEvent(); +} + sol! { function emptyFunction(); } diff --git a/crates/sol-types/tests/ui/event.rs b/crates/sol-types/tests/ui/event.rs new file mode 100644 index 0000000000..9afe266205 --- /dev/null +++ b/crates/sol-types/tests/ui/event.rs @@ -0,0 +1,51 @@ +use alloy_sol_types::sol; + +sol! { + event MissingParens1 +} + +sol! { + event MissingParens2 anonymous; +} + +sol! { + event MissingParens3; +} + +sol! { + event MissingSemi1() +} + +sol! { + event MissingSemi2() anonymous +} + +sol! { + event FourIndexedParameters(bool indexed, bool indexed, bool indexed, bool indexed); +} + +sol! { + event FiveIndexedParameters(bool indexed, bool indexed, bool indexed, bool indexed, bool indexed); +} + +sol! { + event FourIndexedParametersAnonymous(bool indexed, bool indexed, bool indexed, bool indexed) anonymous; +} + +sol! { + event FiveIndexedParametersAnonymous(bool indexed, bool indexed, bool indexed, bool indexed, bool indexed) anonymous; +} + +sol! { + event ALotOfParameters(bool, bool, bool, bool, bool, bool, bool, bool, bool, bool); + event ALotOfParametersAnonymous(bool, bool, bool, bool, bool, bool, bool, bool, bool, bool) anonymous; +} + +sol! { + event TrailingComma(uint256,); + event Valid(uint256); +} + +fn main() {} + +struct A {} diff --git a/crates/sol-types/tests/ui/event.stderr b/crates/sol-types/tests/ui/event.stderr new file mode 100644 index 0000000000..e4b6147325 --- /dev/null +++ b/crates/sol-types/tests/ui/event.stderr @@ -0,0 +1,59 @@ +error: unexpected end of input, expected parentheses + --> tests/ui/event.rs:3:1 + | +3 | / sol! { +4 | | event MissingParens1 +5 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected parentheses + --> tests/ui/event.rs:8:26 + | +8 | event MissingParens2 anonymous; + | ^^^^^^^^^ + +error: expected parentheses + --> tests/ui/event.rs:12:25 + | +12 | event MissingParens3; + | ^ + +error: expected `;` + --> tests/ui/event.rs:15:1 + | +15 | / sol! { +16 | | event MissingSemi1() +17 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected `;` + --> tests/ui/event.rs:19:1 + | +19 | / sol! { +20 | | event MissingSemi2() anonymous +21 | | } + | |_^ + | + = note: this error originates in the macro `sol` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: more than 3 indexed arguments for event + --> tests/ui/event.rs:24:11 + | +24 | event FourIndexedParameters(bool indexed, bool indexed, bool indexed, bool indexed); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: more than 3 indexed arguments for event + --> tests/ui/event.rs:28:11 + | +28 | event FiveIndexedParameters(bool indexed, bool indexed, bool indexed, bool indexed, bool indexed); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: more than 4 indexed arguments for anonymous event + --> tests/ui/event.rs:36:11 + | +36 | event FiveIndexedParametersAnonymous(bool indexed, bool indexed, bool indexed, bool indexed, bool indexed) anonymous; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/sol-types/tests/ui/storage.rs b/crates/sol-types/tests/ui/storage.rs index 18e747aa7c..7c3cd6bf1c 100644 --- a/crates/sol-types/tests/ui/storage.rs +++ b/crates/sol-types/tests/ui/storage.rs @@ -1,63 +1,115 @@ use alloy_sol_types::sol; -sol! { - struct BadMemoryStruct { - bool memory a; - } -} +compile_error!("No fail cases"); sol! { - struct BadStorageStruct { - bool storage a; + struct Custom { + bool a; } -} -sol! { - struct BadCalldataStruct { - bool calldata a; + struct ValidStorageStruct { + bool memory a; + bool storage b; + bool calldata c; + + bytes memory d; + bytes storage e; + bytes calldata f; + + string memory g; + string storage h; + string calldata i; + + bool[] memory j; + bool[] storage k; + bool[] calldata l; + + Custom memory m; + Custom storage n; + Custom calldata o; + + Custom[3] memory p; + Custom[3] storage q; + Custom[3] calldata r; } -} -sol! { - function badMemoryFunction( + function validStorageFunction( bool memory a, - ); -} + bool storage b, + bool calldata c, -sol! { - function badStorageFunction( - bool storage a, + bytes memory d, + bytes storage e, + bytes calldata f, + + string memory g, + string storage h, + string calldata i, + + bool[] memory j, + bool[] storage k, + bool[] calldata l, + + Custom memory m, + Custom storage n, + Custom calldata o, + + Custom[3] memory p, + Custom[3] storage q, + Custom[3] calldata r, ); -} -sol! { - function badCalldataFunction( - bool calldata a, + error validStorageError( + bool memory a, + bool storage b, + bool calldata c, + + bytes memory d, + bytes storage e, + bytes calldata f, + + string memory g, + string storage h, + string calldata i, + + bool[] memory j, + bool[] storage k, + bool[] calldata l, + + Custom memory m, + Custom storage n, + Custom calldata o, + + Custom[3] memory p, + Custom[3] storage q, + Custom[3] calldata r, ); -} -sol! { - struct Custom { - bool a; - } + error validStorageEvent( + bool memory a, + bool storage b, + bool calldata c, - function validStorage( - bytes memory a, - bytes storage b, - bytes calldata c, + bytes memory d, + bytes storage e, + bytes calldata f, - string memory d, - string storage e, - string calldata f, + string memory g, + string storage h, + string calldata i, - bool[] memory g, - bool[] storage h, - bool[] calldata i, + bool[] memory j, + bool[] storage k, + bool[] calldata l, - Custom memory j, - Custom storage k, - Custom calldata l, - ) external; + Custom memory m, + Custom storage n, + Custom calldata o, + + Custom[3] memory p, + Custom[3] storage q, + Custom[3] calldata r, + ); } fn main() {} diff --git a/crates/sol-types/tests/ui/storage.stderr b/crates/sol-types/tests/ui/storage.stderr index 8578409fe7..23198db424 100644 --- a/crates/sol-types/tests/ui/storage.stderr +++ b/crates/sol-types/tests/ui/storage.stderr @@ -1,35 +1,5 @@ -error: data locations are not allowed in struct definitions - --> tests/ui/storage.rs:5:14 +error: No fail cases + --> tests/ui/storage.rs:3:1 | -5 | bool memory a; - | ^^^^^^ - -error: data locations are not allowed in struct definitions - --> tests/ui/storage.rs:11:14 - | -11 | bool storage a; - | ^^^^^^^ - -error: data locations are not allowed in struct definitions - --> tests/ui/storage.rs:17:14 - | -17 | bool calldata a; - | ^^^^^^^^ - -error: data location can only be specified for array, struct or mapping types - --> tests/ui/storage.rs:23:14 - | -23 | bool memory a, - | ^^^^^^ - -error: data location can only be specified for array, struct or mapping types - --> tests/ui/storage.rs:29:14 - | -29 | bool storage a, - | ^^^^^^^ - -error: data location can only be specified for array, struct or mapping types - --> tests/ui/storage.rs:35:14 - | -35 | bool calldata a, - | ^^^^^^^^ +3 | compile_error!("No fail cases"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/syn-solidity/README.md b/crates/syn-solidity/README.md index 49f71a13e6..85de2b9150 100644 --- a/crates/syn-solidity/README.md +++ b/crates/syn-solidity/README.md @@ -21,14 +21,22 @@ correctly by `syn-solidity`. However, `syn-solidity` is more permissive and lenient compared to the official Solidity compiler and grammar specifications. Some examples of code patterns that are valid in `syn-solidity` but not in the official compiler include: +- identifiers are Rust identifiers (`syn::Ident`), and as such cannot contain + the dollar sign (`$`), but can contain unicode characters - trailing punctuation, like commas (`,`) in function arguments or enums definitions - certain variable and function attributes in certain contexts, like `internal` functions or functions with implementations (`{ ... }`) in interfaces +- parameter storage locations in item definitions, like `uint256[] memory` in + a struct or error definition +- the tuple type `(T, U, ..)` is allowed wherever a type is expected, and can + optionally be preceded by the `tuple` keyword. + This is the same as [`ethers.js`'s Human-Readable ABI][ethersjs-abi] This lenient behavior is intentionally designed to facilitate usage within procedural macros, and to reduce general code complexity in the parser and AST. +[ethersjs-abi]: https://docs.ethers.org/v5/api/utils/abi/formats/#abi-formats--human-readable-abi [^1]: Older versions may still parse successfully, but this is not guaranteed. ## Example diff --git a/crates/syn-solidity/src/ident/mod.rs b/crates/syn-solidity/src/ident/mod.rs index 07b26f6458..23ef6e18ca 100644 --- a/crates/syn-solidity/src/ident/mod.rs +++ b/crates/syn-solidity/src/ident/mod.rs @@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span}; use quote::ToTokens; use std::fmt; use syn::{ + ext::IdentExt, parse::{Parse, ParseStream}, Result, }; @@ -91,4 +92,9 @@ impl SolIdent { } s } + + /// See `[Ident::peek_any]`. + pub fn peek_any(input: ParseStream<'_>) -> bool { + input.peek(Ident::peek_any) + } } diff --git a/crates/syn-solidity/src/item/contract.rs b/crates/syn-solidity/src/item/contract.rs index 4cb4bd8b75..5be81a7e61 100644 --- a/crates/syn-solidity/src/item/contract.rs +++ b/crates/syn-solidity/src/item/contract.rs @@ -28,6 +28,7 @@ pub struct ItemContract { impl fmt::Debug for ItemContract { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Contract") + .field("attrs", &self.attrs) .field("kind", &self.kind) .field("name", &self.name) .field("inheritance", &self.inheritance) diff --git a/crates/syn-solidity/src/item/error.rs b/crates/syn-solidity/src/item/error.rs index 338d692c8e..ab195dde6e 100644 --- a/crates/syn-solidity/src/item/error.rs +++ b/crates/syn-solidity/src/item/error.rs @@ -18,15 +18,16 @@ pub struct ItemError { pub error_token: kw::error, pub name: SolIdent, pub paren_token: Paren, - pub fields: Parameters, + pub parameters: Parameters, pub semi_token: Token![;], } impl fmt::Debug for ItemError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Error") + .field("attrs", &self.attrs) .field("name", &self.name) - .field("fields", &self.fields) + .field("fields", &self.parameters) .finish() } } @@ -39,7 +40,7 @@ impl Parse for ItemError { error_token: input.parse()?, name: input.parse()?, paren_token: parenthesized!(content in input), - fields: content.parse()?, + parameters: content.parse()?, semi_token: input.parse()?, }) } @@ -55,6 +56,6 @@ impl ItemError { } pub fn as_type(&self) -> Type { - Type::Tuple(self.fields.types().cloned().collect()) + Type::Tuple(self.parameters.types().cloned().collect()) } } diff --git a/crates/syn-solidity/src/item/event.rs b/crates/syn-solidity/src/item/event.rs new file mode 100644 index 0000000000..6d96fc039f --- /dev/null +++ b/crates/syn-solidity/src/item/event.rs @@ -0,0 +1,204 @@ +use crate::{kw, utils::DebugPunctuated, ParameterList, SolIdent, Type, VariableDeclaration}; +use proc_macro2::Span; +use std::fmt; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Paren, + Attribute, Error, Result, Token, +}; + +#[derive(Clone)] +pub struct ItemEvent { + pub attrs: Vec, + pub event_token: kw::event, + pub name: SolIdent, + pub paren_token: Paren, + pub parameters: Punctuated, + pub anonymous: Option, + pub semi_token: Token![;], +} + +impl fmt::Debug for ItemEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ItemEvent") + .field("attrs", &self.attrs) + .field("name", &self.name) + .field("arguments", DebugPunctuated::new(&self.parameters)) + .field("anonymous", &self.anonymous.is_some()) + .finish() + } +} + +impl Parse for ItemEvent { + fn parse(input: ParseStream<'_>) -> Result { + let content; + Ok(Self { + attrs: input.call(Attribute::parse_outer)?, + event_token: input.parse()?, + name: input.parse()?, + paren_token: parenthesized!(content in input), + parameters: content.parse_terminated(EventParameter::parse, Token![,])?, + anonymous: input.parse()?, + semi_token: input.parse()?, + }) + } +} + +impl ItemEvent { + pub fn span(&self) -> Span { + self.name.span() + } + + pub fn set_span(&mut self, span: Span) { + self.name.set_span(span); + } + + /// Returns `true` if the event is anonymous. + #[inline] + pub const fn is_anonymous(&self) -> bool { + self.anonymous.is_some() + } + + /// Returns the maximum amount of indexed parameters this event can have. + /// + /// This is `4` if the event is anonymous, otherwise `3`. + #[inline] + pub fn max_indexed(&self) -> usize { + if self.is_anonymous() { + 4 + } else { + 3 + } + } + + /// Returns `true` if the event has more indexed parameters than allowed by + /// Solidity. + /// + /// See [`Self::max_indexed`]. + #[inline] + pub fn exceeds_max_indexed(&self) -> bool { + self.indexed_params().count() > self.max_indexed() + } + + /// Asserts that the event has a valid amount of indexed parameters. + pub fn assert_valid(&self) -> Result<()> { + if self.exceeds_max_indexed() { + let msg = if self.is_anonymous() { + "more than 4 indexed arguments for anonymous event" + } else { + "more than 3 indexed arguments for event" + }; + Err(Error::new(self.span(), msg)) + } else { + Ok(()) + } + } + + pub fn params(&self) -> ParameterList { + self.parameters + .iter() + .map(EventParameter::as_param) + .collect() + } + + pub fn non_indexed_params(&self) -> impl Iterator { + self.parameters.iter().filter(|p| !p.is_indexed()) + } + + pub fn indexed_params(&self) -> impl Iterator { + self.parameters.iter().filter(|p| p.is_indexed()) + } + + pub fn dynamic_params(&self) -> impl Iterator { + self.parameters.iter().filter(|p| p.is_dynamic()) + } + + pub fn as_type(&self) -> Type { + Type::Tuple(self.parameters.iter().map(|arg| arg.ty.clone()).collect()) + } +} + +/// An event parameter. +/// +/// ` [indexed] [name]` +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct EventParameter { + pub ty: Type, + pub indexed: Option, + pub name: Option, +} + +impl fmt::Debug for EventParameter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EventParameter") + .field("ty", &self.ty) + .field("indexed", &self.indexed.is_some()) + .field("name", &self.name) + .finish() + } +} + +impl Parse for EventParameter { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + ty: input.parse()?, + indexed: input.parse()?, + name: if SolIdent::peek_any(input) { + Some(input.parse()?) + } else { + None + }, + }) + } +} + +impl EventParameter { + pub fn span(&self) -> Span { + let span = self.ty.span(); + self.name + .as_ref() + .and_then(|name| span.join(name.span())) + .unwrap_or(span) + } + + pub fn set_span(&mut self, span: Span) { + self.ty.set_span(span); + if let Some(kw) = &mut self.indexed { + kw.span = span; + } + if let Some(name) = &mut self.name { + name.set_span(span); + } + } + + pub fn as_param(&self) -> VariableDeclaration { + VariableDeclaration { + name: self.name.clone(), + storage: None, + ty: self.ty.clone(), + } + } + + /// Returns `true` if the parameter is indexed. + #[inline] + pub const fn is_indexed(&self) -> bool { + self.indexed.is_some() + } + + /// Returns `true` if the event parameter has to be stored in the data + /// section. + /// + /// From [the Solidity reference][ref]: + /// + /// > all “complex” types or types of dynamic length, including all arrays, + /// > string, bytes and structs + /// + /// and all types that are not `indexed`. + /// + /// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#events + pub const fn is_dynamic(&self) -> bool { + !self.is_indexed() || self.ty.is_event_dynamic() + } +} diff --git a/crates/syn-solidity/src/item/function.rs b/crates/syn-solidity/src/item/function.rs index 0497740f6c..3d1b33ca09 100644 --- a/crates/syn-solidity/src/item/function.rs +++ b/crates/syn-solidity/src/item/function.rs @@ -8,8 +8,8 @@ use syn::{ Attribute, Error, Result, Token, }; -/// A function definition: -/// `function helloWorld() external pure returns(string memory);` +/// A function definition: `function helloWorld() external pure returns(string +/// memory);` /// /// Solidity reference: /// @@ -31,6 +31,7 @@ pub struct ItemFunction { impl fmt::Debug for ItemFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Function") + .field("attrs", &self.attrs) .field("name", &self.name) .field("arguments", &self.arguments) .field("attributes", &self.attributes) diff --git a/crates/syn-solidity/src/item/mod.rs b/crates/syn-solidity/src/item/mod.rs index 04c433c406..e9645da8f1 100644 --- a/crates/syn-solidity/src/item/mod.rs +++ b/crates/syn-solidity/src/item/mod.rs @@ -11,8 +11,11 @@ pub use contract::ItemContract; mod error; pub use error::ItemError; +mod event; +pub use event::{EventParameter, ItemEvent}; + mod function; -pub use function::ItemFunction; +pub use function::{ItemFunction, Returns}; mod r#struct; pub use r#struct::ItemStruct; @@ -32,8 +35,12 @@ pub enum Item { /// An error definition: `error Foo(uint256 a, uint256 b);` Error(ItemError), - /// A function definition: - /// `function helloWorld() external pure returns(string memory);` + /// An event definition: `event Transfer(address indexed from, address + /// indexed to, uint256 value);` + Event(ItemEvent), + + /// A function definition: `function helloWorld() external pure + /// returns(string memory);` Function(ItemFunction), /// A struct definition: `struct Foo { uint256 bar; }` @@ -52,6 +59,8 @@ impl Parse for Item { input.parse().map(Self::Function) } else if lookahead.peek(Token![struct]) { input.parse().map(Self::Struct) + } else if lookahead.peek(kw::event) { + input.parse().map(Self::Event) } else if lookahead.peek(kw::error) { input.parse().map(Self::Error) } else if contract::ContractKind::peek(&lookahead) { @@ -74,6 +83,7 @@ impl Item { match self { Self::Contract(contract) => contract.span(), Self::Error(error) => error.span(), + Self::Event(event) => event.span(), Self::Function(function) => function.span(), Self::Struct(strukt) => strukt.span(), Self::Udt(udt) => udt.span(), @@ -84,6 +94,7 @@ impl Item { match self { Self::Contract(contract) => contract.set_span(span), Self::Error(error) => error.set_span(span), + Self::Event(event) => event.set_span(span), Self::Function(function) => function.set_span(span), Self::Struct(strukt) => strukt.set_span(span), Self::Udt(udt) => udt.set_span(span), @@ -98,6 +109,10 @@ impl Item { matches!(self, Self::Error(_)) } + pub fn is_event(&self) -> bool { + matches!(self, Self::Event(_)) + } + pub fn is_function(&self) -> bool { matches!(self, Self::Function(_)) } @@ -114,6 +129,7 @@ impl Item { match self { Self::Contract(ItemContract { name, .. }) | Self::Error(ItemError { name, .. }) + | Self::Event(ItemEvent { name, .. }) | Self::Function(ItemFunction { name, .. }) | Self::Struct(ItemStruct { name, .. }) | Self::Udt(ItemUdt { name, .. }) => name, @@ -125,6 +141,7 @@ impl Item { Self::Contract(ItemContract { attrs, .. }) | Self::Function(ItemFunction { attrs, .. }) | Self::Error(ItemError { attrs, .. }) + | Self::Event(ItemEvent { attrs, .. }) | Self::Struct(ItemStruct { attrs, .. }) | Self::Udt(ItemUdt { attrs, .. }) => attrs, } @@ -135,6 +152,7 @@ impl Item { Self::Contract(ItemContract { attrs, .. }) | Self::Function(ItemFunction { attrs, .. }) | Self::Error(ItemError { attrs, .. }) + | Self::Event(ItemEvent { attrs, .. }) | Self::Struct(ItemStruct { attrs, .. }) | Self::Udt(ItemUdt { attrs, .. }) => attrs, } @@ -145,6 +163,7 @@ impl Item { Self::Struct(strukt) => Some(strukt.as_type()), Self::Udt(udt) => Some(udt.ty.clone()), Self::Error(error) => Some(error.as_type()), + Self::Event(event) => Some(event.as_type()), Self::Contract(_) | Self::Function(_) => None, } } diff --git a/crates/syn-solidity/src/item/struct.rs b/crates/syn-solidity/src/item/struct.rs index bb99746a4d..709a09a947 100644 --- a/crates/syn-solidity/src/item/struct.rs +++ b/crates/syn-solidity/src/item/struct.rs @@ -42,6 +42,7 @@ impl Hash for ItemStruct { impl fmt::Debug for ItemStruct { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Struct") + .field("attrs", &self.attrs) .field("name", &self.name) .field("fields", &self.fields) .finish() diff --git a/crates/syn-solidity/src/item/udt.rs b/crates/syn-solidity/src/item/udt.rs index 1a37367cbd..ffad7ed0bb 100644 --- a/crates/syn-solidity/src/item/udt.rs +++ b/crates/syn-solidity/src/item/udt.rs @@ -26,6 +26,7 @@ pub struct ItemUdt { impl fmt::Debug for ItemUdt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Udt") + .field("attrs", &self.attrs) .field("name", &self.name) .field("ty", &self.ty) .finish() diff --git a/crates/syn-solidity/src/lib.rs b/crates/syn-solidity/src/lib.rs index 06817a7ef0..ae8f694813 100644 --- a/crates/syn-solidity/src/lib.rs +++ b/crates/syn-solidity/src/lib.rs @@ -28,7 +28,10 @@ mod ident; pub use ident::{SolIdent, SolPath}; mod item; -pub use item::{Item, ItemContract, ItemError, ItemFunction, ItemStruct, ItemUdt}; +pub use item::{ + EventParameter, Item, ItemContract, ItemError, ItemEvent, ItemFunction, ItemStruct, ItemUdt, + Returns, +}; mod r#type; pub use r#type::{SolArray, SolTuple, Type}; @@ -53,12 +56,10 @@ pub mod kw { use syn::custom_keyword; pub use syn::token::{Abstract, Override, Virtual}; - custom_keyword!(is); - - // Types - custom_keyword!(error); - custom_keyword!(function); - custom_keyword!(tuple); + // Storage + custom_keyword!(memory); + custom_keyword!(storage); + custom_keyword!(calldata); // Visibility custom_keyword!(external); @@ -72,19 +73,29 @@ pub mod kw { custom_keyword!(constant); custom_keyword!(payable); + // Contract + custom_keyword!(contract); + custom_keyword!(interface); + custom_keyword!(library); + + // Error + custom_keyword!(error); + + // Event + custom_keyword!(event); + custom_keyword!(indexed); + custom_keyword!(anonymous); + // Function custom_keyword!(immutable); custom_keyword!(returns); + custom_keyword!(function); - // Storage - custom_keyword!(memory); - custom_keyword!(storage); - custom_keyword!(calldata); + // Types + custom_keyword!(tuple); - // Contract - custom_keyword!(contract); - custom_keyword!(interface); - custom_keyword!(library); + // Other + custom_keyword!(is); } /// Parse a Solidity [`proc_macro::TokenStream`] into a [`File`]. diff --git a/crates/syn-solidity/src/macros.rs b/crates/syn-solidity/src/macros.rs index bc1da29e64..d3cfe7d15f 100644 --- a/crates/syn-solidity/src/macros.rs +++ b/crates/syn-solidity/src/macros.rs @@ -65,6 +65,7 @@ macro_rules! make_visitor { match item { Item::Contract(contract) => v.visit_item_contract(contract), Item::Error(error) => v.visit_item_error(error), + Item::Event(event) => v.visit_item_event(event), Item::Function(function) => v.visit_item_function(function), Item::Struct(strukt) => v.visit_item_struct(strukt), Item::Udt(udt) => v.visit_item_udt(udt), @@ -80,7 +81,17 @@ macro_rules! make_visitor { fn visit_item_error(&mut v, error: &'ast $($mut)? ItemError) { v.visit_ident(& $($mut)? error.name); - v.visit_parameter_list(& $($mut)? error.fields); + v.visit_parameter_list(& $($mut)? error.parameters); + } + + fn visit_item_event(&mut v, event: &'ast $($mut)? ItemEvent) { + v.visit_ident(& $($mut)? event.name); + for EventParameter { name, ty, .. } in & $($mut)? event.parameters { + v.visit_type(ty); + if let Some(name) = name { + v.visit_ident(name); + } + } } fn visit_item_function(&mut v, function: &'ast $($mut)? ItemFunction) { diff --git a/crates/syn-solidity/src/type.rs b/crates/syn-solidity/src/type.rs index 2f0f1e8375..bd69e37b77 100644 --- a/crates/syn-solidity/src/type.rs +++ b/crates/syn-solidity/src/type.rs @@ -80,6 +80,14 @@ impl SolArray { } } + /// See [`Type::is_abi_dynamic`]. + pub fn is_abi_dynamic(&self) -> bool { + match self.size { + Some(_) => false, + None => self.ty.is_abi_dynamic(), + } + } + pub fn wrap(input: ParseStream<'_>, ty: Type) -> Result { let content; Ok(Self { @@ -193,6 +201,11 @@ impl SolTuple { } self.paren_token = Paren(span); } + + /// See [`Type::is_abi_dynamic`]. + pub fn is_abi_dynamic(&self) -> bool { + self.types.iter().any(Type::is_abi_dynamic) + } } /// A type name. @@ -200,32 +213,26 @@ impl SolTuple { /// Solidity reference: #[derive(Clone)] pub enum Type { - /// `address [payable]` + /// `address $(payable)?` Address(Span, Option), /// `bool` Bool(Span), /// `string` String(Span), - /// `Some(size) => bytes`, `None => bytes` - Bytes { - span: Span, - size: Option, - }, - /// `Some(size) => int`, `None => int` - Int { - span: Span, - size: Option, - }, - /// `Some(size) => uint`, `None => uint` - Uint { - span: Span, - size: Option, - }, - - /// `Some(size) => []`, `None => []` + /// `bytes` + Bytes(Span), + /// `bytes` + FixedBytes(Span, NonZeroU16), + + /// `int[size]` + Int(Span, Option), + /// `uint[size]` + Uint(Span, Option), + + /// `$ty[$(size)?]` Array(SolArray), - /// `(tuple)? ( $($type),* )` + /// `$(tuple)? ( $($types,)* )` Tuple(SolTuple), // TODO: function type @@ -240,12 +247,16 @@ impl PartialEq for Type { (Self::Address(..), Self::Address(..)) => true, (Self::Bool(_), Self::Bool(_)) => true, (Self::String(_), Self::String(_)) => true, - (Self::Bytes { size: a, .. }, Self::Bytes { size: b, .. }) => a == b, - (Self::Int { size: a, .. }, Self::Int { size: b, .. }) => a == b, - (Self::Uint { size: a, .. }, Self::Uint { size: b, .. }) => a == b, + (Self::Bytes { .. }, Self::Bytes { .. }) => true, + + (Self::FixedBytes(_, a), Self::FixedBytes(_, b)) => a == b, + (Self::Int(_, a), Self::Int(_, b)) => a == b, + (Self::Uint(_, a), Self::Uint(_, b)) => a == b, + (Self::Tuple(a), Self::Tuple(b)) => a == b, (Self::Array(a), Self::Array(b)) => a == b, (Self::Custom(a), Self::Custom(b)) => a == b, + _ => false, } } @@ -257,10 +268,12 @@ impl Hash for Type { fn hash(&self, state: &mut H) { std::mem::discriminant(self).hash(state); match self { - Self::Address(..) | Self::Bool(_) | Self::String(_) => {} - Self::Bytes { size, .. } => size.hash(state), - Self::Int { size, .. } => size.hash(state), - Self::Uint { size, .. } => size.hash(state), + Self::Address(..) | Self::Bool(_) | Self::String(_) | Self::Bytes(_) => {} + + Self::FixedBytes(_, size) => size.hash(state), + Self::Int(_, size) => size.hash(state), + Self::Uint(_, size) => size.hash(state), + Self::Tuple(tuple) => tuple.hash(state), Self::Array(array) => array.hash(state), Self::Custom(custom) => custom.hash(state), @@ -275,9 +288,12 @@ impl fmt::Debug for Type { Self::Address(_, Some(_)) => f.write_str("AddressPayable"), Self::Bool(_) => f.write_str("Bool"), Self::String(_) => f.write_str("String"), - Self::Bytes { size, .. } => f.debug_tuple("Bytes").field(size).finish(), - Self::Int { size, .. } => f.debug_tuple("Int").field(size).finish(), - Self::Uint { size, .. } => f.debug_tuple("Uint").field(size).finish(), + Self::Bytes(_) => f.write_str("Bytes"), + + Self::FixedBytes(_, size) => f.debug_tuple("FixedBytes").field(size).finish(), + Self::Int(_, size) => f.debug_tuple("Int").field(size).finish(), + Self::Uint(_, size) => f.debug_tuple("Uint").field(size).finish(), + Self::Tuple(tuple) => tuple.fmt(f), Self::Array(array) => array.fmt(f), Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(), @@ -288,13 +304,15 @@ impl fmt::Debug for Type { impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Address(_, None) => f.write_str("address"), - Self::Address(_, Some(_)) => f.write_str("address payable"), + Self::Address(_, _) => f.write_str("address"), Self::Bool(_) => f.write_str("bool"), Self::String(_) => f.write_str("string"), - Self::Bytes { size, .. } => write_opt(f, "bytes", *size), - Self::Int { size, .. } => write_opt(f, "int", *size), - Self::Uint { size, .. } => write_opt(f, "uint", *size), + Self::Bytes(_) => f.write_str("bytes"), + + Self::FixedBytes(_, size) => write!(f, "bytes{size}"), + Self::Int(_, size) => write_opt(f, "int", *size), + Self::Uint(_, size) => write_opt(f, "uint", *size), + Self::Tuple(tuple) => tuple.fmt(f), Self::Array(array) => array.fmt(f), Self::Custom(custom) => custom.fmt(f), @@ -321,7 +339,8 @@ impl Parse for Type { Some(Some(size)) if size.get() > 32 => { return Err(Error::new(span, "fixed bytes range is 1-32")) } - Some(size) => Self::Bytes { span, size }, + Some(None) => Self::Bytes(span), + Some(Some(size)) => Self::FixedBytes(span, size), } } else if let Some(s) = s.strip_prefix("int") { match parse_size(s, span)? { @@ -332,7 +351,7 @@ impl Parse for Type { "intX must be a multiple of 8 up to 256", )) } - Some(size) => Self::Int { span, size }, + Some(size) => Self::Int(span, size), } } else if let Some(s) = s.strip_prefix("uint") { match parse_size(s, span)? { @@ -343,7 +362,7 @@ impl Parse for Type { "uintX must be a multiple of 8 up to 256", )) } - Some(size) => Self::Uint { span, size }, + Some(size) => Self::Uint(span, size), } } else { Self::custom(ident) @@ -379,9 +398,10 @@ impl Type { } Self::Bool(span) | Self::String(span) - | Self::Bytes { span, .. } - | Self::Int { span, .. } - | Self::Uint { span, .. } => *span, + | Self::Bytes(span) + | Self::FixedBytes(span, _) + | Self::Int(span, _) + | Self::Uint(span, _) => *span, Self::Tuple(tuple) => tuple.span(), Self::Array(array) => array.span(), Self::Custom(custom) => custom.span(), @@ -398,30 +418,61 @@ impl Type { } Self::Bool(span) | Self::String(span) - | Self::Bytes { span, .. } - | Self::Int { span, .. } - | Self::Uint { span, .. } => *span = new_span, + | Self::Bytes(span) + | Self::FixedBytes(span, _) + | Self::Int(span, _) + | Self::Uint(span, _) => *span = new_span, + Self::Tuple(tuple) => tuple.set_span(new_span), Self::Array(array) => array.set_span(new_span), Self::Custom(custom) => custom.set_span(new_span), } } - /// Returns whether a [Storage][crate::Storage] location can be - /// specified for this type. - pub const fn can_have_storage(&self) -> bool { - self.is_dynamic() || self.is_custom() - } - - pub const fn is_dynamic(&self) -> bool { + /// Returns whether this type is ABI-encoded as a single EVM word (32 + /// bytes). + pub const fn is_one_word(&self) -> bool { matches!( self, - Self::String(_) | Self::Bytes { size: None, .. } | Self::Array(_) + Self::Address(..) + | Self::Bool(_) + | Self::Int(..) + | Self::Uint(..) + | Self::FixedBytes(..) ) } + /// Returns whether this type is encoded in the data section of an event. + pub const fn is_event_dynamic(&self) -> bool { + !self.is_one_word() + } + + /// Returns whether this type is dynamic according to ABI rules. + pub fn is_abi_dynamic(&self) -> bool { + match self { + Self::Address(..) + | Self::Bool(_) + | Self::Int(..) + | Self::Uint(..) + | Self::FixedBytes(..) => false, + + Self::String(_) | Self::Bytes(_) | Self::Custom(_) => true, + + Self::Tuple(tuple) => tuple.is_abi_dynamic(), + Self::Array(array) => array.is_abi_dynamic(), + } + } + + pub const fn is_array(&self) -> bool { + matches!(self, Self::Array(_)) + } + + pub const fn is_tuple(&self) -> bool { + matches!(self, Self::Tuple(_)) + } + pub const fn is_custom(&self) -> bool { - matches!(self, Self::Custom(..)) + matches!(self, Self::Custom(_)) } /// Traverses this type while calling `f`. diff --git a/crates/syn-solidity/src/variable.rs b/crates/syn-solidity/src/variable.rs index 5aa0fcf14d..b2b6abb1ae 100644 --- a/crates/syn-solidity/src/variable.rs +++ b/crates/syn-solidity/src/variable.rs @@ -1,6 +1,5 @@ use super::{kw, SolIdent, Storage, Type}; use proc_macro2::Span; -use quote::format_ident; use std::{ fmt::{self, Write}, ops::{Deref, DerefMut}, @@ -9,7 +8,7 @@ use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, - Error, Ident, Result, Token, + Ident, Result, Token, }; /// A list of comma-separated [VariableDeclaration]s. @@ -52,18 +51,9 @@ impl

fmt::Debug for Parameters

{ /// Parameter list: fields names are set to `_{index}` impl Parse for Parameters { fn parse(input: ParseStream<'_>) -> Result { - let mut list = input.parse_terminated(VariableDeclaration::parse, Token![,])?; - - // Set names for anonymous parameters - for (i, var) in list.iter_mut().enumerate() { - if var.name.is_none() { - let mut ident = format_ident!("_{i}"); - ident.set_span(var.span()); - var.name = Some(SolIdent(ident)); - } - } - - Ok(Self(list)) + input + .parse_terminated(VariableDeclaration::parse, Token![,]) + .map(Self) } } @@ -108,6 +98,12 @@ impl<'a, P> IntoIterator for &'a mut Parameters

{ } } +impl FromIterator for Parameters

{ + fn from_iter>(iter: T) -> Self { + Self(Punctuated::from_iter(iter)) + } +} + impl

Parameters

{ pub const fn new() -> Self { Self(Punctuated::new()) @@ -140,6 +136,12 @@ impl

Parameters

{ self.iter_mut().map(|var| &mut var.ty) } + pub fn types_and_names( + &self, + ) -> impl ExactSizeIterator)> + DoubleEndedIterator { + self.iter().map(|p| (&p.ty, p.name.as_ref())) + } + pub fn type_strings( &self, ) -> impl ExactSizeIterator + DoubleEndedIterator + Clone + '_ { @@ -235,24 +237,13 @@ impl VariableDeclaration { } fn _parse(input: ParseStream<'_>, for_struct: bool) -> Result { - let ty = input.parse::()?; - let can_have_storage = ty.can_have_storage(); - let this = Self { - ty, + Ok(Self { + ty: input.parse()?, storage: if input.peek(kw::memory) || input.peek(kw::storage) || input.peek(kw::calldata) { - let storage = input.parse::()?; - if for_struct || !can_have_storage { - let msg = if for_struct { - "data locations are not allowed in struct definitions" - } else { - "data location can only be specified for array, struct or mapping types" - }; - return Err(Error::new(storage.span(), msg)) - } - Some(storage) + Some(input.parse()?) } else { None }, @@ -262,7 +253,6 @@ impl VariableDeclaration { } else { None }, - }; - Ok(this) + }) } }