Skip to content

Commit

Permalink
feat(sol-macro): SolEventInterface: SolInterface for contract eve…
Browse files Browse the repository at this point in the history
…nts enum (#426)

* refactor: split event logic to separate fn

* chore: clean up verbatim impl

* wip

* feat: SolEventInterface

* fix: event names

* nostd

* docs
  • Loading branch information
DaniPopes authored Nov 22, 2023
1 parent 6c7a63c commit 389e256
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 53 deletions.
7 changes: 6 additions & 1 deletion crates/primitives/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub struct Log {
pub data: Bytes,
}

#[allow(clippy::missing_const_for_fn)]
impl Log {
/// Creates a new log, without length-checking. This allows creation of
/// invalid logs. May be safely used when the length of the topic list is
Expand All @@ -36,34 +35,40 @@ impl Log {
}

/// True if valid, false otherwise.
#[inline]
pub fn is_valid(&self) -> bool {
self.topics.len() <= 4
}

/// Get the topic list.
#[inline]
pub fn topics(&self) -> &[B256] {
&self.topics
}

/// Get the topic list, mutably. This gives access to the internal
/// array, without allowing extension of that array.
#[inline]
pub fn topics_mut(&mut self) -> &mut [B256] {
&mut self.topics
}

/// Get a mutable reference to the topic list. This allows creation of
/// invalid logs.
#[inline]
pub fn topics_mut_unchecked(&mut self) -> &mut Vec<B256> {
&mut self.topics
}

/// Set the topic list, without length-checking. This allows creation of
/// invalid logs.
#[inline]
pub fn set_topics_unchecked(&mut self, topics: Vec<B256>) {
self.topics = topics;
}

/// Set the topic list, truncating to 4 topics.
#[inline]
pub fn set_topics_truncating(&mut self, mut topics: Vec<B256>) {
topics.truncate(4);
self.set_topics_unchecked(topics);
Expand Down
90 changes: 78 additions & 12 deletions crates/sol-macro/src/expand/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,18 +357,17 @@ impl<'a> ToExpand<'a> {

impl<'a> CallLikeExpander<'a> {
fn expand(&self, to_expand: ToExpand<'_>, attrs: Vec<Attribute>) -> TokenStream {
let data @ ExpandData { name, variants, min_data_len, trait_, .. } =
&to_expand.to_data(self);
let types = data.types();
let name_s = name.to_string();
let count = variants.len();
let def = self.generate_enum(data, attrs);
let data = &to_expand.to_data(self);

// TODO: SolInterface for events
if matches!(to_expand, ToExpand::Events(_)) {
return def;
if let ToExpand::Events(events) = to_expand {
return self.expand_events(events, data, attrs);
}

let def = self.generate_enum(data, attrs);
let ExpandData { name, variants, min_data_len, trait_, .. } = data;
let types = data.types();
let name_s = name.to_string();
let count = data.variants.len();
quote! {
#def

Expand Down Expand Up @@ -402,10 +401,10 @@ impl<'a> CallLikeExpander<'a> {
validate: bool
)-> ::alloy_sol_types::Result<Self> {
match selector {
#(<#types as ::alloy_sol_types::#trait_>::SELECTOR => {
#(<#types as ::alloy_sol_types::#trait_>::SELECTOR =>
<#types as ::alloy_sol_types::#trait_>::abi_decode_raw(data, validate)
.map(Self::#variants)
})*
.map(Self::#variants),
)*
s => ::core::result::Result::Err(::alloy_sol_types::Error::unknown_selector(
<Self as ::alloy_sol_types::SolInterface>::NAME,
s,
Expand All @@ -432,6 +431,73 @@ impl<'a> CallLikeExpander<'a> {
}
}

fn expand_events(
&self,
events: &[&ItemEvent],
data: &ExpandData,
attrs: Vec<Attribute>,
) -> TokenStream {
let def = self.generate_enum(data, attrs);
let ExpandData { name, trait_, .. } = data;
let name_s = name.to_string();
let count = data.variants.len();

let has_anon = events.iter().any(|e| e.is_anonymous());
let has_non_anon = events.iter().any(|e| !e.is_anonymous());
assert!(has_anon || has_non_anon, "events shouldn't be empty");

let e_name = |&e: &&ItemEvent| self.cx.overloaded_name(e.into());
let err = quote! {
::alloy_sol_types::private::Err(::alloy_sol_types::Error::InvalidLog {
name: <Self as ::alloy_sol_types::SolEventInterface>::NAME,
log: ::alloy_sol_types::private::Box::new(::alloy_sol_types::private::Log::new_unchecked(
topics.to_vec(),
data.to_vec().into(),
)),
})
};
let non_anon_impl = has_non_anon.then(|| {
let variants = events.iter().filter(|e| !e.is_anonymous()).map(e_name);
let ret = has_anon.then(|| quote!(return));
let ret_err = (!has_anon).then_some(&err);
quote! {
match topics.first().copied() {
#(
Some(<#variants as ::alloy_sol_types::#trait_>::SIGNATURE_HASH) =>
#ret <#variants as ::alloy_sol_types::#trait_>::decode_log(topics, data, validate)
.map(Self::#variants),
)*
_ => { #ret_err }
}
}
});
let anon_impl = has_anon.then(|| {
let variants = events.iter().filter(|e| e.is_anonymous()).map(e_name);
quote! {
#(
if let Ok(res) = <#variants as ::alloy_sol_types::#trait_>::decode_log(topics, data, validate) {
return Ok(Self::#variants(res));
}
)*
#err
}
});

quote! {
#def

impl ::alloy_sol_types::SolEventInterface for #name {
const NAME: &'static str = #name_s;
const COUNT: usize = #count;

fn decode_log(topics: &[::alloy_sol_types::Word], data: &[u8], validate: bool) -> ::alloy_sol_types::Result<Self> {
#non_anon_impl
#anon_impl
}
}
}
}

fn generate_enum(&self, data: &ExpandData, mut attrs: Vec<Attribute>) -> TokenStream {
let ExpandData { name, variants, selectors, .. } = data;
let types = data.types();
Expand Down
69 changes: 44 additions & 25 deletions crates/sol-macro/src/verbatim.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,63 @@
use std::collections::BTreeMap;

use proc_macro2::TokenStream;
use quote::quote;
use std::collections::BTreeMap;

/// Converts the given value into tokens that represent itself.
pub fn verbatim<T: Verbatim>(t: &T) -> TokenStream {
let mut s = TokenStream::new();
t.to_tokens(&mut s);
s
t.to_verbatim_token_stream()
}

/// Conversion to tokens that represent the value itself.
pub trait Verbatim {
fn to_tokens(&self, s: &mut TokenStream);
/// Converts `self` into tokens that represent itself.
fn to_verbatim_tokens(&self, s: &mut TokenStream);

/// Converts `self` into a [`TokenStream`] that represents itself.
fn to_verbatim_token_stream(&self) -> TokenStream {
let mut s = TokenStream::new();
self.to_verbatim_tokens(&mut s);
s
}

/// Uses [`Verbatim::to_verbatim_tokens`] to provide a [`quote::ToTokens`] implementation.
#[inline]
fn verbatim(&self) -> ToTokensCompat<'_, Self> {
fn quote_verbatim(&self) -> ToTokensCompat<'_, Self> {
ToTokensCompat(self)
}

/// Uses [`Verbatim::to_verbatim_tokens`] to provide a [`quote::ToTokens`] implementation.
#[inline]
fn into_verbatim(self) -> IntoTokensCompat<Self>
fn into_quote_verbatim(self) -> IntoTokensCompat<Self>
where
Self: Sized,
{
IntoTokensCompat(self)
}
}

/// Provides a [`quote::ToTokens`] implementations for references of values that implement
/// [`Verbatim`].
pub struct ToTokensCompat<'a, T: ?Sized + Verbatim>(pub &'a T);

impl<T: Verbatim> quote::ToTokens for ToTokensCompat<'_, T> {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
self.0.to_verbatim_tokens(tokens)
}
}

/// Provides a [`quote::ToTokens`] implementations for owned values that implement [`Verbatim`].
pub struct IntoTokensCompat<T: ?Sized + Verbatim>(pub T);

impl<T: Verbatim> quote::ToTokens for IntoTokensCompat<T> {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
self.0.to_verbatim_tokens(tokens)
}
}

impl Verbatim for String {
fn to_tokens(&self, tokens: &mut TokenStream) {
fn to_verbatim_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(if self.is_empty() {
quote!(::alloy_sol_types::private::String::new())
} else {
Expand All @@ -56,39 +68,47 @@ impl Verbatim for String {

impl Verbatim for bool {
#[inline]
fn to_tokens(&self, s: &mut TokenStream) {
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
quote::ToTokens::to_tokens(self, s)
}
}

impl Verbatim for usize {
#[inline]
fn to_tokens(&self, s: &mut TokenStream) {
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
quote::ToTokens::to_tokens(self, s)
}
}

impl<T: Verbatim> Verbatim for Vec<T> {
fn to_tokens(&self, s: &mut TokenStream) {
let iter = self.iter().map(ToTokensCompat);
s.extend(quote!(::alloy_sol_types::private::vec![#(#iter),*]));
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
s.extend(if self.is_empty() {
quote!(::alloy_sol_types::private::Vec::new())
} else {
let iter = self.iter().map(ToTokensCompat);
quote!(::alloy_sol_types::private::vec![#(#iter),*])
});
}
}

impl<K: Verbatim, V: Verbatim> Verbatim for BTreeMap<K, V> {
fn to_tokens(&self, s: &mut TokenStream) {
let k = self.keys().map(ToTokensCompat);
let v = self.values().map(ToTokensCompat);
s.extend(quote!(::alloy_sol_types::private::BTreeMap::from([#( (#k, #v) ),*])));
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
s.extend(if self.is_empty() {
quote!(::alloy_sol_types::private::BTreeMap::new())
} else {
let k = self.keys().map(ToTokensCompat);
let v = self.values().map(ToTokensCompat);
quote!(::alloy_sol_types::private::BTreeMap::from([#( (#k, #v) ),*]))
});
}
}

impl<T: Verbatim> Verbatim for Option<T> {
fn to_tokens(&self, s: &mut TokenStream) {
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
let tts = match self {
Some(t) => {
let mut s = TokenStream::new();
t.to_tokens(&mut s);
t.to_verbatim_tokens(&mut s);
quote!(::core::option::Option::Some(#s))
}
None => quote!(::core::option::Option::None),
Expand All @@ -102,7 +122,7 @@ macro_rules! derive_verbatim {

(struct $name:ident { $($field:ident),* $(,)? } $($rest:tt)*) => {
impl Verbatim for alloy_json_abi::$name {
fn to_tokens(&self, s: &mut TokenStream) {
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
let Self { $($field),* } = self;
$(
let $field = ToTokensCompat($field);
Expand All @@ -119,7 +139,7 @@ macro_rules! derive_verbatim {

(enum $name:ident { $($variant:ident $( { $($field_idx:tt : $field:ident),* $(,)? } )?),* $(,)? } $($rest:tt)*) => {
impl Verbatim for alloy_json_abi::$name {
fn to_tokens(&self, s: &mut TokenStream) {
fn to_verbatim_tokens(&self, s: &mut TokenStream) {
match self {$(
Self::$variant $( { $($field_idx: $field),* } )? => {
$($(
Expand All @@ -137,7 +157,6 @@ macro_rules! derive_verbatim {
}

derive_verbatim! {
// struct JsonAbi { constructor, functions, events, errors, receive, fallback }
struct Constructor { inputs, state_mutability }
struct Fallback { state_mutability }
struct Receive { state_mutability }
Expand Down
1 change: 1 addition & 0 deletions crates/sol-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ serde = { workspace = true, optional = true, features = ["derive"] }

[dev-dependencies]
alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] }
derive_more.workspace = true
paste.workspace = true
pretty_assertions.workspace = true
serde = { workspace = true, features = ["derive"] }
Expand Down
23 changes: 22 additions & 1 deletion crates/sol-types/src/abi/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ pub trait TokenSeq<'a>: Token<'a> {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct WordToken(pub Word);

impl<T> From<&T> for WordToken
where
T: Clone,
Self: From<T>,
{
#[inline]
fn from(value: &T) -> Self {
Self::from(value.clone())
}
}

impl<T> From<&mut T> for WordToken
where
T: Clone,
Self: From<T>,
{
#[inline]
fn from(value: &mut T) -> Self {
Self::from(value.clone())
}
}

impl From<Word> for WordToken {
#[inline]
fn from(value: Word) -> Self {
Expand Down Expand Up @@ -368,7 +390,6 @@ impl<'de, T: Token<'de>> Token<'de> for DynSeqToken<T> {
}

impl<'de, T: Token<'de>> TokenSeq<'de> for DynSeqToken<T> {
#[inline]
fn encode_sequence(&self, enc: &mut Encoder) {
let head_words = self.0.iter().map(Token::head_words).sum::<usize>();
enc.push_offset(head_words);
Expand Down
Loading

0 comments on commit 389e256

Please sign in to comment.