From 599a09a0639bc8aa3a94c2d676667c0f23d85c1c Mon Sep 17 00:00:00 2001 From: malik Date: Sat, 30 Nov 2024 05:01:07 +0100 Subject: [PATCH] test --- evm/abi/src/arg.rs | 2 +- examples/log.rs | 123 ++++++++++++++-- zink/codegen/Cargo.toml | 2 +- zink/codegen/src/event.rs | 299 ++++++++++++++++++++++++++++---------- zink/codegen/src/lib.rs | 2 +- 5 files changed, 338 insertions(+), 90 deletions(-) diff --git a/evm/abi/src/arg.rs b/evm/abi/src/arg.rs index 45f88cbb6..d7700166e 100644 --- a/evm/abi/src/arg.rs +++ b/evm/abi/src/arg.rs @@ -111,7 +111,7 @@ impl fmt::Display for Param { } #[cfg(feature = "syn")] -impl From<&Box> for Param { +impl From<&Box> for Param { fn from(ty: &Box) -> Self { use quote::ToTokens; diff --git a/examples/log.rs b/examples/log.rs index f3a1b7551..5387380fb 100644 --- a/examples/log.rs +++ b/examples/log.rs @@ -3,23 +3,120 @@ extern crate zink; -use zink::{primitives::{Address, U256}, Event}; +use zink::{primitives::Address, Event}; -/// Example events for demonstration +/// ERC20 standard events #[derive(Event)] -enum ERC20Events { - Transfer(Address, Address, U256), - Approval(Address, Address, U256), +pub enum ERC20Events { + /// Emitted when tokens are transferred between addresses + /// Parameters: from, to, value + Transfer(Address, Address, u64), + + /// Emitted when an address approves another address to spend tokens + /// Parameters: owner, spender, value + Approval(Address, Address, u64), } -#[zink::external] -pub fn log_examples(from: Address, to: Address, value: U256) { - let transfer = ERC20Events::Transfer(from, to, value); - transfer; +// /// Implementation of event logging functions +// pub trait ERC20EventLogger { +// fn log_transfer(&self, from: Address, to: Address, value: U256) ; +// fn log_approval(&self, owner: Address, spender: Address, value: U256) ; +// } - let approval = ERC20Events::Approval(from, to, value); - approval; -} +// impl ERC20EventLogger for ERC20Events { +// fn log_transfer(&self, from: Address, to: Address, value: U256) { +// let event = ERC20Events::Transfer(from, to, value); +// let topic = event.topic(); +// let data = event.encode(); +// event.log1(&topic); +// } + +// fn log_approval(&self, owner: Address, spender: Address, value: U256) { +// let event = ERC20Events::Approval(owner, spender, value); +// let topic = event.topic(); +// let data = event.encode(); +// event.log1(&topic); +// } +// } + +// /// Example contract functions demonstrating event logging +// #[zink::external] +// pub mod erc20_events { +// use super::*; + +// /// Logs a transfer event +// pub fn log_transfer(from: Address, to: Address, value: U256) { +// let events = ERC20Events::Transfer(from, to, value); +// events.log_transfer(from, to, value); +// } + +// /// Logs an approval event +// pub fn log_approval(owner: Address, spender: Address, value: U256) { +// let events = ERC20Events::Approval(owner, spender, value); +// events.log_approval(owner, spender, value); +// } + +// /// Example of logging multiple events in a single transaction +// pub fn log_transfer_and_approval( +// from: Address, +// to: Address, +// spender: Address, +// value: U256, +// ) { +// // Log transfer first +// log_transfer(from, to, value)?; +// // Then log approval +// log_approval(from, spender, value)?; +// Ok(()) +// } +// } + +// #[cfg(test)] +// mod tests { +// use super::*; +// use zink::test_utils::{Address as TestAddress, U256 as TestU256}; + +// #[test] +// fn test_event_signatures() { +// let transfer = ERC20Events::Transfer( +// TestAddress::zero(), +// TestAddress::zero(), +// TestU256::from(0), +// ); +// let approval = ERC20Events::Approval( +// TestAddress::zero(), +// TestAddress::zero(), +// TestU256::from(0), +// ); + +// assert_eq!( +// transfer.abi_signature(), +// "Transfer(address,address,uint256)" +// ); +// assert_eq!( +// approval.abi_signature(), +// "Approval(address,address,uint256)" +// ); +// } + +// #[test] +// fn test_event_logging() -> Result<(), EventError> { +// let from = TestAddress::from([1u8; 20]); +// let to = TestAddress::from([2u8; 20]); +// let spender = TestAddress::from([3u8; 20]); +// let value = TestU256::from(1000); + +// // Test individual event logging +// erc20_events::log_transfer(from, to, value)?; +// erc20_events::log_approval(from, spender, value)?; + +// // Test multiple events +// erc20_events::log_transfer_and_approval(from, to, spender, value)?; + +// Ok(()) +// } +// } +// // Only include main when not targeting wasm32 #[cfg(not(target_arch = "wasm32"))] -fn main() {} \ No newline at end of file +fn main() {} diff --git a/zink/codegen/Cargo.toml b/zink/codegen/Cargo.toml index 767338601..81ec8badd 100644 --- a/zink/codegen/Cargo.toml +++ b/zink/codegen/Cargo.toml @@ -17,6 +17,6 @@ heck.workspace = true hex.workspace = true proc-macro2.workspace = true quote.workspace = true -sha3 = "0.10.8" +sha3 = "0.10.8" syn.workspace = true zabi = { workspace = true, features = [ "hex", "syn" ] } diff --git a/zink/codegen/src/event.rs b/zink/codegen/src/event.rs index 13507b661..088039ee9 100644 --- a/zink/codegen/src/event.rs +++ b/zink/codegen/src/event.rs @@ -1,66 +1,124 @@ -//! Event interface generation use proc_macro::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use sha3::{Digest, Keccak256}; -use syn::{Data, DeriveInput, Fields, LitByteStr, Variant}; +use syn::{ + parse_quote, spanned::Spanned, Data, DeriveInput, Error, Fields, LitByteStr, Result, Type, + Variant, +}; -/// Expand the event interface +/// Custom error type for better error handling +#[derive(Debug)] +enum EventError { + NotEnum(Span), + UnsupportedType(Span, String), + TooManyFields(Span), +} + +impl EventError { + fn to_compile_error(&self) -> TokenStream { + let error_msg = match self { + Self::NotEnum(span) => { + Error::new((*span).into(), "Event can only be derived for enums") + } + Self::UnsupportedType(span, ty) => { + Error::new((*span).into(), format!("Unsupported type: {}", ty)) + } + Self::TooManyFields(span) => { + Error::new((*span).into(), "Too many fields for event logging") + } + }; + error_msg.to_compile_error().into() + } +} + +/// Expand the event interface with better error handling pub fn parse(item: DeriveInput) -> TokenStream { + match parse_impl(item) { + Ok(token_stream) => token_stream, + Err(err) => err.to_compile_error().into(), + } +} + +fn parse_impl(item: DeriveInput) -> Result { let name = LitByteStr::new(item.ident.to_string().as_bytes(), Span::call_site().into()); let ident = item.clone().ident; // Ensure we are working with an enum let event_enum = match &item.data { Data::Enum(data_enum) => data_enum, - _ => panic!("Event can only be derived for enums"), + _ => { + return Err(Error::new_spanned( + &item, + "Event can only be derived for enums", + )) + } }; let enum_name = &item.ident; - // Generate ABI signature - let abi_signature = generate_abi_signature(enum_name, &event_enum.variants); + // Generate ABI signature with validation + let abi_signature = generate_abi_signature(enum_name, &event_enum.variants)?; + // Generate variant implementations with validation let variant_implementations = event_enum .variants .iter() .map(|variant| generate_variant_implementation(enum_name, variant)) - .collect::>(); + .collect::>>()?; + // Generate the final implementation let expanded = quote! { impl zink::Event for #ident { const NAME: &'static [u8] = #name; - // ABI signature generation - fn abi_signature() -> String { + pub fn abi_signature() -> String { #abi_signature } - // Logging methods - fn log0(&self) { + fn log0(&self) -> Result<(), zink::EventError> { match self { #(#variant_implementations)* } } - fn log1(&self, topic: &'static [u8]) { + fn log1(&self, topic: &[u8]) -> Result<(), zink::EventError> { + if topic.len() != 32 { + return Err(zink::EventError::InvalidTopicLength); + } match self { #(#variant_implementations)* } } - fn log2(&self, topic1: &'static [u8], topic2: &'static [u8]) { + fn log2(&self, topic1: &[u8], topic2: &[u8]) -> Result<(), zink::EventError> { + if topic1.len() != 32 || topic2.len() != 32 { + return Err(zink::EventError::InvalidTopicLength); + } match self { #(#variant_implementations)* } } - fn log3(&self, topic1: &'static [u8], topic2: &'static [u8], topic3: &'static [u8]) { + fn log3(&self, topic1: &[u8], topic2: &[u8], topic3: &[u8]) -> Result<(), zink::EventError> { + if topic1.len() != 32 || topic2.len() != 32 || topic3.len() != 32 { + return Err(zink::EventError::InvalidTopicLength); + } match self { #(#variant_implementations)* } } - fn log4(&self, topic1: &'static [u8], topic2: &'static [u8], topic3: &'static [u8], topic4: &'static [u8]) { + fn log4( + &self, + topic1: &[u8], + topic2: &[u8], + topic3: &[u8], + topic4: &[u8] + ) -> Result<(), zink::EventError> { + if topic1.len() != 32 || topic2.len() != 32 || + topic3.len() != 32 || topic4.len() != 32 { + return Err(zink::EventError::InvalidTopicLength); + } match self { #(#variant_implementations)* } @@ -68,107 +126,200 @@ pub fn parse(item: DeriveInput) -> TokenStream { } }; - expanded.into() + Ok(expanded.into()) } -/// Generate Variant Implementation +/// Generate Variant Implementation with validation fn generate_variant_implementation( enum_name: &syn::Ident, variant: &Variant, -) -> proc_macro2::TokenStream { +) -> Result { let variant_name = &variant.ident; + let span = variant.span(); match &variant.fields { Fields::Named(fields) => { + if fields.named.len() > 4 { + return Err(Error::new(span, "Named event can have at most 4 fields")); + } + let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect(); - quote! { + let field_types: Vec<_> = fields.named.iter().map(|f| &f.ty).collect(); + + validate_types(&field_types)?; + + Ok(quote! { #enum_name::#variant_name { #(#field_names),* } => { let topic = generate_topic_hash(stringify!(#variant_name)); - let data = vec![ - #( - format!("{:?}", #field_names).into_bytes() - ),* + let data: Vec> = vec![ + #(encode_field(&#field_names)?),* ]; - let flattened_data: Vec = data.concat(); - zink::ffi::evm::log2(&topic, &generate_data_hash(&data), &flattened_data) + let flattened_data = flatten_and_pad_data(&data)?; + zink::ffi::evm::log1(&topic, &flattened_data) + .map_err(|e| zink::EventError::LogError(e)) } - } + }) } Fields::Unnamed(fields) => { - let field_indices = 0..fields.unnamed.len(); - let field_indices_clone = field_indices.clone(); - quote! { - #enum_name::#variant_name(#(field #field_indices_clone),*) => { + if fields.unnamed.len() > 4 { + return Err(Error::new( + variant.span(), + "Tuple event can have at most 4 fields", + )); + } + + // Use a consistent binding pattern for tuple variants with explicit ref patterns + let field_count = fields.unnamed.len(); + let field_bindings = (0..field_count) + .map(|i| quote::format_ident!("v{}", i)) + .collect::>(); + let ref_patterns = field_bindings + .iter() + .map(|id| quote!(ref #id)) + .collect::>(); + + let field_types: Vec<_> = fields.unnamed.iter().map(|f| &f.ty).collect(); + validate_types(&field_types)?; + + Ok(quote! { + #enum_name::#variant_name(#(#ref_patterns),*) => unsafe { let topic = generate_topic_hash(stringify!(#variant_name)); let data = vec![ - #( - format!("{:?}", field #field_indices).into_bytes() - ),* + #(encode_field(#field_bindings)?),* ]; - let flattened_data: Vec = data.concat(); - zink::ffi::evm::log2(&topic, &generate_data_hash(&data), &flattened_data) + zink::ffi::evm::log1(&topic, &data) + .map_err(|e| zink::EventError::LogError(e)) } - } + }) } - Fields::Unit => { - quote! { - #enum_name::#variant_name => { - zink::ffi::evm::log0(&generate_topic_hash(stringify!(#variant_name))) - } + Fields::Unit => Ok(quote! { + #enum_name::#variant_name => { + let topic = generate_topic_hash(stringify!(#variant_name)); + zink::ffi::evm::log0(&topic) + .map_err(|e| zink::EventError::LogError(e)) } + }), + } +} + +/// Validate field types +fn validate_types(types: &[&Type]) -> Result<()> { + for ty in types { + if !is_supported_type(ty) { + return Err(Error::new_spanned( + ty, + format!("Unsupported type for event field: {}", quote!(#ty)), + )); } } + Ok(()) } +/// Check if type is supported +fn is_supported_type(ty: &Type) -> bool { + matches!( + type_to_string(ty).as_str(), + "u8" | "u16" + | "u32" + | "u64" + | "u128" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "bool" + | "String" + | "Vec" + | "&str" + | "&[u8]" + | "[u8;32]" + | "Address" + | "U256" + ) +} -/// Generate ABI signature +/// Generate ABI signature with validation fn generate_abi_signature( - enum_name: &syn::Ident, - variants: &syn::punctuated::Punctuated -) -> proc_macro2::TokenStream { - let variant_signatures = variants.iter().map(|variant| { - let variant_name = &variant.ident; - let params = match &variant.fields { - Fields::Named(fields) => { - fields.named.iter() - .map(|f| type_to_string(&f.ty)) - .collect::>() - .join(",") - }, - Fields::Unnamed(fields) => { - fields.unnamed.iter() - .map(|f| type_to_string(&f.ty)) - .collect::>() - .join(",") - }, - Fields::Unit => String::new() - }; - - format!("{}({})", variant_name, params) - }).collect::>(); + enum_name: &syn::Ident, + variants: &syn::punctuated::Punctuated, +) -> Result { + let variant_signatures = variants + .iter() + .map(|variant| { + let variant_name = &variant.ident; + let params = match &variant.fields { + Fields::Named(fields) => fields + .named + .iter() + .map(|f| validate_and_convert_type(&f.ty)) + .collect::>>()? + .join(","), + Fields::Unnamed(fields) => fields + .unnamed + .iter() + .map(|f| validate_and_convert_type(&f.ty)) + .collect::>>()? + .join(","), + Fields::Unit => String::new(), + }; - quote! { + Ok(format!("{}({})", variant_name, params)) + }) + .collect::>>()?; + + Ok(quote! { vec![ #(#variant_signatures.to_string()),* ].join(";") + }) +} + +/// Validate and convert type to ABI string +fn validate_and_convert_type(ty: &Type) -> Result { + let type_str = type_to_string(ty); + match type_str.as_str() { + "u8" | "u16" | "u32" | "u64" => Ok("uint".to_string()), + "i8" | "i16" | "i32" | "i64" => Ok("int".to_string()), + "bool" => Ok("bool".to_string()), + "String" | "&str" => Ok("string".to_string()), + "Vec" | "&[u8]" | "[u8;32]" => Ok("bytes".to_string()), + "Address" => Ok("address".to_string()), + "U256" => Ok("uint256".to_string()), + _ => Err(Error::new_spanned( + ty, + format!("Unsupported type for ABI: {}", type_str), + )), } } +/// Helper function to convert type to string +fn type_to_string(ty: &Type) -> String { + quote!(#ty).to_string().replace([' ', '&'], "") +} + /// Generate topic hash fn generate_topic_hash(input: &str) -> [u8; 32] { Keccak256::digest(input.as_bytes()).into() } /// Generate data hash -pub fn generate_data_hash(data: &[Vec]) -> [u8; 32] { +fn generate_data_hash(data: &[Vec]) -> [u8; 32] { let flattened: Vec = data.concat(); Keccak256::digest(&flattened).into() } -fn type_to_string(ty: &syn::Type) -> String { - // Use quote to convert the type to a token stream, then to a string - let type_tokens = quote! { #ty }; - type_tokens.to_string() - .replace(' ', "") - .replace("&", "") +/// Helper function to flatten and pad data +fn flatten_and_pad_data(data: &[Vec]) -> Result> { + let mut result = Vec::new(); + for chunk in data { + if chunk.len() > 32 { + // return Err(zink::EventError::DataTooLong); + panic!("Data too long"); + } + let mut padded = vec![0u8; 32]; + padded[..chunk.len()].copy_from_slice(chunk); + result.extend_from_slice(&padded); + } + Ok(result) } diff --git a/zink/codegen/src/lib.rs b/zink/codegen/src/lib.rs index 50fbbfb0c..38b8e877d 100644 --- a/zink/codegen/src/lib.rs +++ b/zink/codegen/src/lib.rs @@ -11,7 +11,7 @@ mod revert; mod selector; mod storage; mod utils; - + /// Revert with the input message /// /// Only raw string is supported, formatter currently doesn't work.