Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(sol-macro): SolEventInterface: SolInterface for contract events enum #426

Merged
merged 7 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


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