Skip to content

Commit

Permalink
feat: Solidity events support (#83)
Browse files Browse the repository at this point in the history
* chore: add tests, format attrs

* feat: parse events

* Prestwich/events (#87)

* fix: event trait fixes

* fix: re-exporting event

* refactor: event trait take 2

* refactor: improve topic detokenization

* refactor: change events to account for indexed structs not in body

* fix: move test to bottom of event

* name

* chore: cleanup

* docs

* assign anon variables names in expansion

* wip

* refactor: implement `SolType` for `{Ui,I}nt<N>` and `FixedBytes<N>` with const-generics

* chore: clippy

* chore: add more trait bounds

* clean up code

* cleanups

* finish EventTopic impls

* fix: impl for empty tuple

* chore: clippy

* rm unsafe

* review

* Type only tuple variants, move is_dynamic methods to Type

* chore: clippy

* docs

---------

Co-authored-by: James Prestwich <james@prestwi.ch>
  • Loading branch information
DaniPopes and prestwich authored Jun 12, 2023
1 parent dd969b0 commit 747533d
Show file tree
Hide file tree
Showing 41 changed files with 1,597 additions and 383 deletions.
315 changes: 262 additions & 53 deletions crates/sol-macro/src/expand/mod.rs

Large diffs are not rendered by default.

55 changes: 24 additions & 31 deletions crates/sol-macro/src/expand/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
}
Expand Down Expand Up @@ -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}"),
Expand All @@ -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 {
Expand All @@ -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!()
}
},
}
}
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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!()
}
},
}
}
Expand Down
7 changes: 6 additions & 1 deletion crates/sol-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
39 changes: 33 additions & 6 deletions crates/sol-macro/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -13,10 +13,12 @@ pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> [u8; 32] {
output
}

pub fn selector<T: AsRef<[u8]>>(bytes: T) -> TokenStream {
let hash = keccak256(bytes);
let selector: [u8; 4] = hash[..4].try_into().unwrap();
quote!([#(#selector),*])
pub fn selector<T: AsRef<[u8]>>(bytes: T) -> impl ToTokens {
ExprArray::<_, 4>::new(keccak256(bytes)[..4].try_into().unwrap())
}

pub fn event_selector<T: AsRef<[u8]>>(bytes: T) -> impl ToTokens {
ExprArray::new(keccak256(bytes))
}

pub fn combine_errors(v: Vec<syn::Error>) -> Option<syn::Error> {
Expand All @@ -25,3 +27,28 @@ pub fn combine_errors(v: Vec<syn::Error>) -> Option<syn::Error> {
a
})
}

struct ExprArray<T, const N: usize> {
array: [T; N],
span: Span,
}

impl<T, const N: usize> ExprArray<T, N> {
fn new(array: [T; N]) -> Self {
Self {
array,
span: Span::call_site(),
}
}
}

impl<T: ToTokens, const N: usize> ToTokens for ExprArray<T, N> {
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);
}
})
}
}
2 changes: 1 addition & 1 deletion crates/sol-types/src/coder/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
20 changes: 8 additions & 12 deletions crates/sol-types/src/coder/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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() )+
}
}
Expand All @@ -526,15 +524,15 @@ macro_rules! tuple_impls {

#[inline]
fn total_words(&self) -> usize {
let ($(ref $ty,)+) = *self;
let ($($ty,)+) = self;
0 $( + $ty.total_words() )+
}

fn head_append(&self, enc: &mut Encoder) {
if Self::is_dynamic() {
enc.append_indirection();
} else {
let ($(ref $ty,)+) = *self;
let ($($ty,)+) = self;
$(
$ty.head_append(enc);
)+
Expand All @@ -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);
Expand All @@ -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);
$(
Expand All @@ -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 {
Expand Down Expand Up @@ -630,6 +624,8 @@ impl TokenSeq for () {
}
}

all_the_tuples!(tuple_impls);

#[cfg(test)]
mod tests {
use super::*;
Expand Down
8 changes: 6 additions & 2 deletions crates/sol-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down
30 changes: 30 additions & 0 deletions crates/sol-types/src/macros.rs
Original file line number Diff line number Diff line change
@@ -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);
};
}
Loading

0 comments on commit 747533d

Please sign in to comment.