Skip to content

Commit

Permalink
feat: add event deserialization with TryFrom EmittedEvent
Browse files Browse the repository at this point in the history
  • Loading branch information
glihm committed Dec 16, 2023
1 parent b3058c9 commit de6d303
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 16 deletions.
9 changes: 8 additions & 1 deletion contracts/src/abicov/components.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ mod simple_component {
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Written: Written
Written: Written,
Written88: WrittenAB,
}

#[derive(Drop, starknet::Event)]
Expand All @@ -30,6 +31,11 @@ mod simple_component {
after: felt252,
}

#[derive(Drop, starknet::Event)]
struct WrittenAB {
data: felt252,
}

#[embeddable_as(Simple)]
impl SimpleImpl<
TContractState, +HasComponent<TContractState>
Expand All @@ -42,6 +48,7 @@ mod simple_component {
let before = self.data.read();
self.data.write(data);
self.emit(Written { before, after: data });
self.emit(WrittenAB { data: 'salut' });
}
}
}
Expand Down
204 changes: 197 additions & 7 deletions crates/rs/src/expand/event.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
use cainome_parser::tokens::{Composite, Token};
use cainome_parser::tokens::{Composite, CompositeInnerKind, Token};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::Ident;
use syn::{LitStr, Type};

use crate::expand::utils;
use crate::expand::types::CairoToRust;
use crate::expand::utils;

pub struct CairoEnumEvent;

/// Expansion of Cairo event enumeration.
impl CairoEnumEvent {
pub fn expand(composite: &Composite, enums: &[Token], structs: &[Token]) -> TokenStream2 {
if !composite.is_event {
return quote!();
}

let depth = 0;
let content = Self::expand_event_enum(composite, depth, enums, structs, None);

let event_name = utils::str_to_ident(&composite.type_name_or_alias());

let snrs_types = utils::snrs_types();
let ccs = utils::cainome_cairo_serde();

quote! {
impl TryFrom<#snrs_types::EmittedEvent> for #event_name {
type Error = String;

fn try_from(event: #snrs_types::EmittedEvent) -> Result<Self, Self::Error> {
use #ccs::CairoSerde;

pub struct CairoEvent;
if event.keys.is_empty() {
return Err("Event has no key".to_string());
}

#content

Err(format!("Could not match any event from keys {:?}", event.keys))
}
}
}

impl CairoEvent {
pub fn expand(enums: &[Token], structs: &[Token]) -> TokenStream2 {
// TODO:
// For each enum in enums -> check if it's an event.
// if yes ->
Expand All @@ -20,6 +51,165 @@ impl CairoEvent {
// the name of the variant of the current enum, and then we've to check
// recursively until the event type is a struct and not an enum.
// - if it's flat, we just take the name of the current variant.
quote!()
}

pub fn expand_event_enum(
composite: &Composite,
depth: usize,
enums: &[Token],
structs: &[Token],
outter_enum: Option<Type>,
) -> TokenStream2 {
let mut variants = vec![];

let event_name_str = composite.type_name_or_alias();
let event_name = utils::str_to_ident(&composite.type_name_or_alias());

for variant in &composite.inners {
let selector_key_offset = utils::str_to_litint(&depth.to_string());

let variant_ident = utils::str_to_ident(&variant.name);
let variant_name_str = utils::str_to_litstr(&variant.name);

let variant_type_path = variant.token.type_path();
let variant_type_name =
utils::str_to_ident(&variant.token.to_composite().unwrap().type_name_or_alias());

let (variant_is_enum, variant_token) = if let Some(t) =
enums.iter().find(|t| t.type_path() == variant_type_path)
{
(true, t)
} else if let Some(t) = structs.iter().find(|t| t.type_path() == variant_type_path) {
(false, t)
} else {
panic!(
"The type {} was not found in existing enums and structs.",
variant_type_path
);
};

let is_flat = variant.kind == CompositeInnerKind::Flat;

// If it's flat, the compiler enforces the type to be an enum.
#[allow(clippy::collapsible_else_if)]
let content = if is_flat {
// TODO: need recursion here...
let outter = utils::str_to_type(&format!("{}::{}", event_name_str, &variant.name));
Self::expand_event_enum(
variant_token.to_composite().unwrap(),
depth,
enums,
structs,
Some(outter),
)
} else {
if variant_is_enum {
// Not flat, check the first key that must match the current variant name.
let outter =
utils::str_to_type(&format!("{}::{}", event_name_str, &variant.name));
let inner_content = Self::expand_event_enum(
variant_token.to_composite().unwrap(),
depth + 1,
enums,
structs,
Some(outter),
);

quote! {
let selector = event.keys[#selector_key_offset];
if selector == starknet::core::utils::get_selector_from_name(#variant_name_str).unwrap_or_else(|_| panic!("Invalid selector for {}", #variant_name_str)) {
#inner_content
}
}
} else {
let (names, desers) = Self::expand_event_struct(
variant_token.to_composite().unwrap(),
variant_name_str.clone(),
);

let end_return = if let Some(ref o) = outter_enum {
quote! {
return Ok(#o(#event_name::#variant_ident(#variant_type_name {
#(#names),*
})))
}
} else {
quote! {
return Ok(#event_name::#variant_ident(#variant_type_name {
#(#names),*
}))
}
};

quote! {
let selector = event.keys[#selector_key_offset];
if selector == starknet::core::utils::get_selector_from_name(#variant_name_str).unwrap_or_else(|_| panic!("Invalid selector for {}", #variant_name_str)) {
let mut key_offset = #selector_key_offset + 1;
let mut data_offset = 0;

#(#desers)*

#end_return
};
}
}
};

variants.push(content);
// If nested + struct -> expand the struct, and selector is key 0.
// If nested + enum -> first check if key 0 is the selector. Then expand enum.
// If flat -> only expand the inner enum without checking first key.
}

quote! {
#(#variants)*
}
}

fn expand_event_struct(
composite: &Composite,
variant_name: LitStr,
) -> (Vec<TokenStream2>, Vec<TokenStream2>) {
let mut desers_tokens = vec![];
let mut names_tokens = vec![];

for inner in &composite.inners {
let name = utils::str_to_ident(&inner.name);
let name_str = utils::str_to_litstr(&inner.name);

let ty = utils::str_to_type(&inner.token.to_rust_type_path());
// Tuples type used as rust type path item path must be surrounded
// by angle brackets.
let ty_punctuated = match inner.token {
Token::Tuple(_) => quote!(<#ty>),
_ => quote!(#ty),
};

match inner.kind {
CompositeInnerKind::Key => {
desers_tokens.push(quote! {
let #name = match #ty_punctuated::cairo_deserialize(&event.keys, key_offset) {
Ok(v) => v,
Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #variant_name, e)),
};
key_offset += #ty_punctuated::cairo_serialized_size(&#name);
});
}
CompositeInnerKind::Data => {
desers_tokens.push(quote! {
let #name = match #ty_punctuated::cairo_deserialize(&event.data, data_offset) {
Ok(v) => v,
Err(e) => return Err(format!("Could not deserialize field {} for {}: {:?}", #name_str, #variant_name, e)),
};
data_offset += #ty_punctuated::cairo_serialized_size(&#name);
});
}
_ => {}
};

names_tokens.push(quote!(#name));
}

(names_tokens, desers_tokens)
}
}
3 changes: 2 additions & 1 deletion crates/rs/src/expand/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
pub(crate) mod contract;
pub(crate) mod r#enum;
// pub(crate) mod event;
pub(crate) mod event;
pub(crate) mod function;
pub(crate) mod r#struct;
mod types;
pub(crate) mod utils;

pub use contract::CairoContract;
pub use event::CairoEnumEvent;
pub use function::CairoFunction;
pub use r#enum::CairoEnum;
pub use r#struct::CairoStruct;
13 changes: 9 additions & 4 deletions crates/rs/src/expand/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Utils function for expansion.
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Ident, Type};
use syn::{Ident, LitInt, LitStr, Type};

///
pub fn str_to_ident(str_in: &str) -> Ident {
Expand All @@ -14,9 +14,14 @@ pub fn str_to_type(str_in: &str) -> Type {
}

///
// pub fn str_to_litstr(str_in: &str) -> LitStr {
// LitStr::new(str_in, proc_macro2::Span::call_site())
// }
pub fn str_to_litstr(str_in: &str) -> LitStr {
LitStr::new(str_in, proc_macro2::Span::call_site())
}

///
pub fn str_to_litint(str_in: &str) -> LitInt {
LitInt::new(str_in, proc_macro2::Span::call_site())
}

pub fn snrs_types() -> Type {
str_to_type("starknet::core::types")
Expand Down
12 changes: 9 additions & 3 deletions crates/rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod macro_inputs;
mod spanned;

use crate::expand::utils;
use crate::expand::{CairoContract, CairoEnum, CairoFunction, CairoStruct};
use crate::expand::{CairoContract, CairoEnum, CairoEnumEvent, CairoFunction, CairoStruct};
use crate::macro_inputs::ContractAbi;

#[proc_macro]
Expand Down Expand Up @@ -43,11 +43,17 @@ fn abigen_internal(input: TokenStream) -> TokenStream {
let e_composite = e.to_composite().expect("composite expected");
tokens.push(CairoEnum::expand_decl(e_composite));
tokens.push(CairoEnum::expand_impl(e_composite));

tokens.push(CairoEnumEvent::expand(
e.to_composite().expect("composite expected"),
enums,
abi_tokens
.get("structs")
.expect("at least one struct expected to expand events"),
));
}
}

// TODO: events need to expand auto-deserialization based on selectors.

let mut reader_views = vec![];
let mut views = vec![];
let mut externals = vec![];
Expand Down

0 comments on commit de6d303

Please sign in to comment.