difftreelog
feat simplify ERC implementations by proc-macro
in: master
4 files changed
crates/evm-coder-macros/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder-macros/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "evm-coder-macros"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+sha3 = "0.9.1"
+quote = "1.0"
+proc-macro2 = "1.0"
+syn = { version = "1.0", features = ["full"] }
+hex = "0.4.3"
+Inflector = "0.11.4"
+darling = "0.13.0"
\ No newline at end of file
crates/evm-coder-macros/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder-macros/src/lib.rs
@@ -0,0 +1,224 @@
+use darling::FromMeta;
+use inflector::cases;
+use proc_macro::TokenStream;
+use quote::quote;
+use sha3::{Digest, Keccak256};
+use syn::{
+ AttributeArgs, DeriveInput, GenericArgument, Ident, ItemTrait, Pat, Path, PathArguments,
+ PathSegment, Type, parse_macro_input, spanned::Spanned,
+};
+
+mod solidity_interface;
+mod to_log;
+
+fn fn_selector_str(input: &str) -> u32 {
+ let mut hasher = Keccak256::new();
+ hasher.update(input.as_bytes());
+ let result = hasher.finalize();
+
+ let mut selector_bytes = [0; 4];
+ selector_bytes.copy_from_slice(&result[0..4]);
+
+ u32::from_be_bytes(selector_bytes)
+}
+
+/// Returns solidity function selector (first 4 bytes of hash) by its
+/// textual representation
+///
+/// ```rs
+/// use evm_coder_macros::fn_selector;
+///
+/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
+/// ```
+#[proc_macro]
+pub fn fn_selector(input: TokenStream) -> TokenStream {
+ let input = input.to_string().replace(' ', "");
+ let selector = fn_selector_str(&input);
+
+ (quote! {
+ #selector
+ })
+ .into()
+}
+
+fn event_selector_str(input: &str) -> [u8; 32] {
+ let mut hasher = Keccak256::new();
+ hasher.update(input.as_bytes());
+ let result = hasher.finalize();
+
+ let mut selector_bytes = [0; 32];
+ selector_bytes.copy_from_slice(&result[0..32]);
+ selector_bytes
+}
+
+/// Returns solidity topic (hash) by its textual representation
+///
+/// ```rs
+/// use evm_coder_macros::event_topic;
+///
+/// assert_eq!(
+/// format!("{:x}", event_topic!(Transfer(address, address, uint256))),
+/// "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+/// );
+/// ```
+#[proc_macro]
+pub fn event_topic(stream: TokenStream) -> TokenStream {
+ let input = stream.to_string().replace(' ', "");
+ let selector_bytes = event_selector_str(&input);
+
+ (quote! {
+ ::primitive_types::H256([#(
+ #selector_bytes,
+ )*])
+ })
+ .into()
+}
+
+fn parse_path(ty: &Type) -> syn::Result<&Path> {
+ match &ty {
+ syn::Type::Path(pat) => {
+ if let Some(qself) = &pat.qself {
+ return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));
+ }
+ Ok(&pat.path)
+ }
+ _ => Err(syn::Error::new(ty.span(), "expected ty to be path")),
+ }
+}
+
+fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {
+ if path.segments.len() != 1 {
+ return Err(syn::Error::new(
+ path.span(),
+ "expected path to have only segment",
+ ));
+ }
+ let last_segment = &path.segments.last().unwrap();
+ Ok(last_segment)
+}
+
+fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {
+ match pat {
+ Pat::Ident(i) => Ok(&i.ident),
+ _ => Err(syn::Error::new(pat.span(), "expected pat ident")),
+ }
+}
+
+fn parse_ident_from_segment(segment: &PathSegment) -> syn::Result<&Ident> {
+ if segment.arguments != PathArguments::None {
+ return Err(syn::Error::new(
+ segment.arguments.span(),
+ "unexpected generic type",
+ ));
+ }
+ Ok(&segment.ident)
+}
+
+fn parse_ident_from_path(path: &Path) -> syn::Result<&Ident> {
+ let segment = parse_path_segment(path)?;
+ parse_ident_from_segment(segment)
+}
+
+fn parse_ident_from_type(ty: &Type) -> syn::Result<&Ident> {
+ let path = parse_path(ty)?;
+ parse_ident_from_path(path)
+}
+
+// Gets T out of Result<T>
+fn parse_result_ok(ty: &Type) -> syn::Result<&Ident> {
+ let path = parse_path(ty)?;
+ let segment = parse_path_segment(path)?;
+
+ if segment.ident != "Result" {
+ return Err(syn::Error::new(
+ ty.span(),
+ "expected Result as return type (no renamed aliases allowed)",
+ ));
+ }
+ let args = match &segment.arguments {
+ PathArguments::AngleBracketed(e) => e,
+ _ => {
+ return Err(syn::Error::new(
+ segment.arguments.span(),
+ "missing Result generics",
+ ))
+ }
+ };
+
+ let args = &args.args;
+ let arg = args.first().unwrap();
+
+ let ty = match arg {
+ GenericArgument::Type(ty) => ty,
+ _ => {
+ return Err(syn::Error::new(
+ arg.span(),
+ "expected first generic to be type",
+ ))
+ }
+ };
+
+ parse_ident_from_type(ty)
+}
+
+fn pascal_ident_to_call(ident: &Ident) -> Ident {
+ let name = format!("{}Call", ident);
+ Ident::new(&name, ident.span())
+}
+fn snake_ident_to_pascal(ident: &Ident) -> Ident {
+ let name = ident.to_string();
+ let name = cases::pascalcase::to_pascal_case(&name);
+ Ident::new(&name, ident.span())
+}
+fn snake_ident_to_screaming(ident: &Ident) -> Ident {
+ let name = ident.to_string();
+ let name = cases::screamingsnakecase::to_screaming_snake_case(&name);
+ Ident::new(&name, ident.span())
+}
+fn pascal_ident_to_snake_call(ident: &Ident) -> Ident {
+ let name = ident.to_string();
+ let name = cases::snakecase::to_snake_case(&name);
+ let name = format!("call_{}", name);
+ Ident::new(&name, ident.span())
+}
+
+fn format_ty(ty: &Ident) -> String {
+ if ty == "string" {
+ format!("{} memory", ty)
+ } else {
+ ty.to_string()
+ }
+}
+
+#[proc_macro_attribute]
+pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {
+ let args = parse_macro_input!(args as AttributeArgs);
+ let args = solidity_interface::InterfaceInfo::from_list(&args).unwrap();
+
+ let input: ItemTrait = match syn::parse(stream) {
+ Ok(t) => t,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
+ match solidity_interface::SolidityInterface::try_from(args, &input) {
+ Ok(v) => v.expand(),
+ Err(e) => e.to_compile_error(),
+ }
+ .into()
+}
+
+#[proc_macro_attribute]
+pub fn solidity(_args: TokenStream, stream: TokenStream) -> TokenStream {
+ stream
+}
+
+#[proc_macro_derive(ToLog, attributes(indexed))]
+pub fn to_log(value: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(value as DeriveInput);
+
+ match to_log::Events::try_from(&input) {
+ Ok(e) => e.expand(),
+ Err(e) => e.to_compile_error(),
+ }
+ .into()
+}
crates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth1use quote::quote;2use darling::FromMeta;3use inflector::cases;4use std::fmt::Write;5use syn::{6 FnArg, Ident, ItemTrait, Meta, NestedMeta, PatType, Path, ReturnType, TraitItem,7 TraitItemMethod, Visibility, spanned::Spanned,8};910use crate::{11 fn_selector_str, format_ty, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,12 parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,13 snake_ident_to_screaming,14};1516struct Is {17 name: Ident,18 pascal_call_name: Ident,19 snake_call_name: Ident,20}21impl Is {22 fn try_from(path: &Path) -> syn::Result<Self> {23 let name = parse_ident_from_path(path)?.clone();24 Ok(Self {25 pascal_call_name: pascal_ident_to_call(&name),26 snake_call_name: pascal_ident_to_snake_call(&name),27 name,28 })29 }3031 fn expand_call_def(&self) -> proc_macro2::TokenStream {32 let name = &self.name;33 let pascal_call_name = &self.pascal_call_name;34 quote! {35 #name(#pascal_call_name)36 }37 }3839 fn expand_interface_id(&self) -> proc_macro2::TokenStream {40 let pascal_call_name = &self.pascal_call_name;41 quote! {42 interface_id ^= #pascal_call_name::interface_id();43 }44 }4546 fn expand_supports_interface(&self) -> proc_macro2::TokenStream {47 let pascal_call_name = &self.pascal_call_name;48 quote! {49 #pascal_call_name::supports_interface(interface_id)50 }51 }5253 fn expand_variant_call(&self) -> proc_macro2::TokenStream {54 let name = &self.name;55 let snake_call_name = &self.snake_call_name;56 quote! {57 InternalCall::#name(call) => return self.#snake_call_name(Msg {58 call,59 caller: c.caller,60 value: c.value,61 })62 }63 }6465 fn expand_call_inner(&self) -> proc_macro2::TokenStream {66 let snake_call_name = &self.snake_call_name;67 let pascal_call_name = &self.pascal_call_name;68 quote! {69 fn #snake_call_name(&mut self, c: Msg<#pascal_call_name>) -> ::core::result::Result<::evm_coder::abi::AbiWriter, Self::Error>;70 }71 }7273 fn expand_parse(&self) -> proc_macro2::TokenStream {74 let name = &self.name;75 let pascal_call_name = &self.pascal_call_name;76 quote! {77 if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {78 return Ok(Some(Self::#name(parsed_call)))79 }80 }81 }82}8384#[derive(Default)]85struct IsList(Vec<Is>);86impl FromMeta for IsList {87 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {88 let mut out = Vec::new();89 for item in items {90 match item {91 NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),92 _ => return Err(syn::Error::new(item.span(), "expected path").into()),93 }94 }95 Ok(Self(out))96 }97}9899#[derive(FromMeta)]100pub struct InterfaceInfo {101 #[darling(default)]102 is: IsList,103 #[darling(default)]104 inline_is: IsList,105 #[darling(default)]106 _events: IsList,107}108109#[derive(FromMeta)]110struct MethodInfo {111 #[darling(default)]112 rename_selector: Option<String>,113}114115struct MethodArg {116 name: Ident,117 ty: Ident,118}119impl MethodArg {120 fn try_from(value: &PatType) -> syn::Result<Self> {121 Ok(Self {122 name: parse_ident_from_pat(&value.pat)?.clone(),123 ty: parse_ident_from_type(&value.ty)?.clone(),124 })125 }126 fn is_value(&self) -> bool {127 self.ty == "value"128 }129 fn is_caller(&self) -> bool {130 self.ty == "caller"131 }132 fn is_special(&self) -> bool {133 self.is_value() || self.is_caller()134 }135 fn selector_ty(&self) -> &Ident {136 assert!(!self.is_special());137 &self.ty138 }139140 fn expand_call_def(&self) -> proc_macro2::TokenStream {141 assert!(!self.is_special());142 let name = &self.name;143 let ty = &self.ty;144145 quote! {146 #name: #ty147 }148 }149150 fn expand_parse(&self) -> proc_macro2::TokenStream {151 assert!(!self.is_special());152 let name = &self.name;153 quote! {154 #name: reader.abi_read()?155 }156 }157158 fn expand_call_arg(&self) -> proc_macro2::TokenStream {159 if self.is_value() {160 quote! {161 c.value.clone()162 }163 } else if self.is_caller() {164 quote! {165 c.caller.clone()166 }167 } else {168 let name = &self.name;169 quote! {170 #name171 }172 }173 }174175 fn solidity_def(&self) -> String {176 assert!(!self.is_special());177 format!("{} {}", format_ty(&self.ty), self.name)178 }179}180181#[derive(PartialEq)]182enum Mutability {183 Mutable,184 View,185 Pure,186}187188struct Method {189 name: Ident,190 camel_name: String,191 pascal_name: Ident,192 screaming_name: Ident,193 selector_str: String,194 selector: u32,195 args: Vec<MethodArg>,196 has_normal_args: bool,197 mutability: Mutability,198 result: Ident,199}200impl Method {201 fn try_from(value: &TraitItemMethod) -> syn::Result<Self> {202 let mut info = MethodInfo {203 rename_selector: None,204 };205 for attr in &value.attrs {206 let ident = parse_ident_from_path(&attr.path)?;207 if ident == "solidity" {208 let args = attr.parse_meta().unwrap();209 info = MethodInfo::from_meta(&args).unwrap();210 } else if ident == "doc" {211 // TODO: Add docs to evm interfaces212 }213 }214 let ident = &value.sig.ident;215 let ident_str = ident.to_string();216 if !cases::snakecase::is_snake_case(&ident_str) {217 return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));218 }219220 let mut mutability = Mutability::Pure;221222 if let Some(FnArg::Receiver(receiver)) = value223 .sig224 .inputs225 .iter()226 .find(|arg| matches!(arg, FnArg::Receiver(_)))227 {228 if receiver.reference.is_none() {229 return Err(syn::Error::new(230 receiver.span(),231 "receiver should be by ref",232 ));233 }234 if receiver.mutability.is_some() {235 mutability = Mutability::Mutable;236 } else {237 mutability = Mutability::View;238 }239 }240 let mut args = Vec::new();241 for typ in value242 .sig243 .inputs244 .iter()245 .filter(|arg| matches!(arg, FnArg::Typed(_)))246 {247 let typ = match typ {248 FnArg::Typed(typ) => typ,249 _ => unreachable!(),250 };251 args.push(MethodArg::try_from(typ)?);252 }253254 if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {255 return Err(syn::Error::new(256 args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),257 "payable function should be mutable",258 ));259 }260261 let result = match &value.sig.output {262 ReturnType::Type(_, ty) => ty,263 _ => return Err(syn::Error::new(value.sig.output.span(), "interface method should return Result<value>\nif there is no value to return - specify void (which is alias to unit)")),264 };265 let result = parse_result_ok(&result)?;266267 let camel_name = info268 .rename_selector269 .unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));270 let mut selector_str = camel_name.clone();271 selector_str.push('(');272 let mut has_normal_args = false;273 for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {274 if i != 0 {275 selector_str.push(',');276 }277 write!(selector_str, "{}", arg.selector_ty()).unwrap();278 has_normal_args = true;279 }280 selector_str.push(')');281 let selector = fn_selector_str(&selector_str);282283 Ok(Self {284 name: ident.clone(),285 camel_name,286 pascal_name: snake_ident_to_pascal(&ident),287 screaming_name: snake_ident_to_screaming(&ident),288 selector_str,289 selector,290 args,291 has_normal_args,292 mutability,293 result: result.clone(),294 })295 }296 fn expand_call_def(&self) -> proc_macro2::TokenStream {297 let defs = self298 .args299 .iter()300 .filter(|a| !a.is_special())301 .map(|a| a.expand_call_def());302 let pascal_name = &self.pascal_name;303304 if self.has_normal_args {305 quote! {306 #pascal_name {307 #(308 #defs,309 )*310 }311 }312 } else {313 quote! {#pascal_name}314 }315 }316317 fn expand_const(&self) -> proc_macro2::TokenStream {318 let screaming_name = &self.screaming_name;319 let selector = self.selector;320 let selector_str = &self.selector_str;321 quote! {322 #[doc = #selector_str]323 const #screaming_name: u32 = #selector;324 }325 }326327 fn expand_interface_id(&self) -> proc_macro2::TokenStream {328 let screaming_name = &self.screaming_name;329 quote! {330 interface_id ^= Self::#screaming_name;331 }332 }333334 fn expand_parse(&self) -> proc_macro2::TokenStream {335 let pascal_name = &self.pascal_name;336 let screaming_name = &self.screaming_name;337 if self.has_normal_args {338 let parsers = self339 .args340 .iter()341 .filter(|a| !a.is_special())342 .map(|a| a.expand_parse());343 quote! {344 Self::#screaming_name => return Ok(Some(Self::#pascal_name {345 #(346 #parsers,347 )*348 }))349 }350 } else {351 quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }352 }353 }354355 fn expand_variant_call(&self) -> proc_macro2::TokenStream {356 let pascal_name = &self.pascal_name;357 let name = &self.name;358359 let matcher = if self.has_normal_args {360 let names = self361 .args362 .iter()363 .filter(|a| !a.is_special())364 .map(|a| &a.name);365366 quote! {{367 #(368 #names,369 )*370 }}371 } else {372 quote! {}373 };374375 let receiver = match self.mutability {376 Mutability::Mutable | Mutability::View => quote! {self.},377 Mutability::Pure => quote! {Self::},378 };379 let args = self.args.iter().map(|a| a.expand_call_arg());380381 quote! {382 InternalCall::#pascal_name #matcher => {383 let result = #receiver #name(384 #(385 #args,386 )*387 )?;388 (&result).abi_write(&mut writer);389 }390 }391 }392393 fn solidity_def(&self) -> String {394 let mut out = format!("function {}(", self.camel_name);395 for (i, arg) in self.args.iter().filter(|a| !a.is_special()).enumerate() {396 if i != 0 {397 out.push_str(", ");398 }399 out.push_str(&arg.solidity_def());400 }401 out.push(')');402 match self.mutability {403 Mutability::Mutable => {}404 Mutability::View => write!(out, " view").unwrap(),405 Mutability::Pure => write!(out, " pure").unwrap(),406 }407 if self.result != "void" {408 write!(out, " returns ({})", format_ty(&self.result)).unwrap();409 }410 out.push(';');411 out412 }413}414415pub struct SolidityInterface {416 vis: Visibility,417 name: Ident,418 info: InterfaceInfo,419 methods: Vec<Method>,420 items: Vec<TraitItem>,421}422impl SolidityInterface {423 pub fn try_from(info: InterfaceInfo, value: &ItemTrait) -> syn::Result<Self> {424 let mut found_error = false;425 let mut methods = Vec::new();426427 for item in &value.items {428 match item {429 TraitItem::Type(ty) => {430 if ty.ident == "Error" {431 found_error = true;432 }433 }434 TraitItem::Method(method) => methods.push(Method::try_from(&method)?),435 _ => {}436 }437 }438 if !found_error {439 return Err(syn::Error::new(440 value.span(),441 "expected associated type called Error, which should implement From<&str>",442 ));443 }444 Ok(Self {445 vis: value.vis.clone(),446 name: value.ident.clone(),447 info,448 methods,449 items: value.items.clone(),450 })451 }452 pub fn expand(self) -> proc_macro2::TokenStream {453 let vis = self.vis;454 let name = self.name;455 let items = self.items;456457 let call_name = pascal_ident_to_call(&name);458459 let call_sub = self460 .info461 .inline_is462 .0463 .iter()464 .chain(self.info.is.0.iter())465 .map(Is::expand_call_def);466 let call_inner = self467 .info468 .inline_is469 .0470 .iter()471 .chain(self.info.is.0.iter())472 .map(Is::expand_call_inner);473 let call_parse = self474 .info475 .inline_is476 .0477 .iter()478 .chain(self.info.is.0.iter())479 .map(Is::expand_parse);480 let call_variants = self481 .info482 .inline_is483 .0484 .iter()485 .chain(self.info.is.0.iter())486 .map(Is::expand_variant_call);487488 let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);489 let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);490491 let calls = self.methods.iter().map(Method::expand_call_def);492 let consts = self.methods.iter().map(Method::expand_const);493 let interface_id = self.methods.iter().map(Method::expand_interface_id);494 let parsers = self.methods.iter().map(Method::expand_parse);495 let call_variants_this = self.methods.iter().map(Method::expand_variant_call);496497 // let methods = self.methods.iter().map(Method::solidity_def);498499 quote! {500 #[derive(Debug)]501 #vis enum #call_name {502 #(503 #calls,504 )*505 #(506 #call_sub,507 )*508 }509 impl #call_name {510 #(511 #consts512 )*513 pub fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::abi::Result<Option<Self>> {514 use ::evm_coder::abi::AbiRead;515 match method_id {516 #(517 #parsers,518 )*519 _ => {},520 }521 #(522 #call_parse523 )else*524 return Ok(None);525 }526 pub const fn interface_id() -> u32 {527 let mut interface_id = 0;528 #(#interface_id)*529 #(#inline_interface_id)*530 interface_id531 }532 pub fn supports_interface(interface_id: u32) -> bool {533 interface_id != 0xffffff && (534 interface_id == Self::interface_id()535 #(536 || #supports_interface537 )*538 )539 }540 }541 #vis trait #name {542 #(543 #items544 )*545 #(546 #call_inner547 )*548 fn call(&mut self, c: Msg<#call_name>) -> ::core::result::Result<::evm_coder::abi::AbiWriter, Self::Error> {549 use ::evm_coder::abi::AbiWrite;550 type InternalCall = #call_name;551 match c.call {552 #(553 #call_variants,554 )*555 _ => {},556 }557 let mut writer = ::evm_coder::abi::AbiWriter::default();558 match c.call {559 #(560 #call_variants_this,561 )*562 _ => unreachable!()563 }564 Ok(writer)565 }566 }567 }568 }569}crates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -0,0 +1,170 @@
+use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
+use std::fmt::Write;
+use quote::quote;
+
+use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};
+
+struct EventField {
+ name: Ident,
+ ty: Ident,
+ indexed: bool,
+}
+
+impl EventField {
+ fn try_from(field: &Field) -> syn::Result<Self> {
+ let name = field.ident.as_ref().unwrap();
+ let ty = parse_ident_from_type(&field.ty)?;
+ let mut indexed = false;
+ for attr in &field.attrs {
+ if let Ok(ident) = parse_ident_from_path(&attr.path) {
+ if ident == "indexed" {
+ indexed = true;
+ }
+ }
+ }
+ Ok(Self {
+ name: name.to_owned(),
+ ty: ty.to_owned(),
+ indexed,
+ })
+ }
+}
+
+struct Event {
+ name: Ident,
+ name_screaming: Ident,
+ fields: Vec<EventField>,
+ selector: [u8; 32],
+ selector_str: String,
+}
+
+impl Event {
+ fn try_from(variant: &Variant) -> syn::Result<Self> {
+ let name = &variant.ident;
+ let name_screaming = snake_ident_to_screaming(&name);
+
+ let named = match &variant.fields {
+ Fields::Named(named) => named,
+ _ => {
+ return Err(syn::Error::new(
+ variant.fields.span(),
+ "expected named fields",
+ ))
+ }
+ };
+ let mut fields = Vec::new();
+ for field in &named.named {
+ fields.push(EventField::try_from(&field)?);
+ }
+ let mut selector_str = format!("{}(", name);
+ for (i, arg) in fields.iter().enumerate() {
+ if i != 0 {
+ write!(selector_str, ",").unwrap();
+ }
+ write!(selector_str, "{}", arg.ty).unwrap();
+ }
+ selector_str.push(')');
+ let selector = crate::event_selector_str(&selector_str);
+
+ Ok(Self {
+ name: name.to_owned(),
+ name_screaming,
+ fields,
+ selector,
+ selector_str,
+ })
+ }
+
+ fn expand_serializers(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+ let name_screaming = &self.name_screaming;
+ let fields = self.fields.iter().map(|f| &f.name);
+
+ let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);
+ let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);
+
+ quote! {
+ Self::#name {#(
+ #fields,
+ )*} => {
+ topics.push(topic::from(Self::#name_screaming));
+ #(
+ topics.push(#indexed.to_topic());
+ )*
+ #(
+ #plain.abi_write(&mut writer);
+ )*
+ }
+ }
+ }
+
+ fn expand_consts(&self) -> proc_macro2::TokenStream {
+ let name_screaming = &self.name_screaming;
+ let selector_str = &self.selector_str;
+ let selector = &self.selector;
+
+ quote! {
+ #[doc = #selector_str]
+ const #name_screaming: [u8; 32] = [#(
+ #selector,
+ )*];
+ }
+ }
+}
+
+pub struct Events {
+ name: Ident,
+ events: Vec<Event>,
+}
+
+impl Events {
+ pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {
+ let name = &data.ident;
+ let en = match &data.data {
+ Data::Enum(en) => en,
+ _ => return Err(syn::Error::new(data.span(), "expected enum")),
+ };
+ let mut events = Vec::new();
+ for variant in &en.variants {
+ events.push(Event::try_from(variant)?);
+ }
+ Ok(Self {
+ name: name.to_owned(),
+ events,
+ })
+ }
+ pub fn expand(&self) -> proc_macro2::TokenStream {
+ let name = &self.name;
+
+ let consts = self.events.iter().map(Event::expand_consts);
+ let serializers = self.events.iter().map(Event::expand_serializers);
+
+ quote! {
+ impl #name {
+ #(
+ #consts
+ )*
+ }
+
+ #[automatically_derived]
+ impl ::evm_coder::events::ToLog for #name {
+ fn to_log(&self, contract: address) -> ::ethereum::Log {
+ use ::evm_coder::events::ToTopic;
+ use ::evm_coder::abi::AbiWrite;
+ let mut writer = ::evm_coder::abi::AbiWriter::new();
+ let mut topics = Vec::new();
+ match self {
+ #(
+ #serializers,
+ )*
+ }
+ ::ethereum::Log {
+ address: contract,
+ topics,
+ data: writer.finish(),
+ }
+ }
+ }
+ }
+ }
+}