diff --git a/crates/dyn-abi/src/dynamic/event.rs b/crates/dyn-abi/src/dynamic/event.rs index 2d1e5b74ba..2d70b0017b 100644 --- a/crates/dyn-abi/src/dynamic/event.rs +++ b/crates/dyn-abi/src/dynamic/event.rs @@ -1,6 +1,6 @@ use crate::{DynSolType, DynSolValue, Error, Result}; use alloc::vec::Vec; -use alloy_primitives::{LogData, B256}; +use alloy_primitives::{IntoLogData, Log, LogData, B256}; /// A dynamic ABI event. /// @@ -8,8 +8,11 @@ use alloy_primitives::{LogData, B256}; /// logs. #[derive(Clone, Debug, PartialEq)] pub struct DynSolEvent { + /// The event signature hash, if any. pub(crate) topic_0: Option, + /// The indexed types. pub(crate) indexed: Vec, + /// The un-indexed types. pub(crate) body: DynSolType, } @@ -103,11 +106,11 @@ impl DynSolEvent { } } - Ok(DecodedEvent { indexed, body }) + Ok(DecodedEvent { selector: self.topic_0, indexed, body }) } /// Decode the event from the given log info. - pub fn decode_log(&self, log: &LogData, validate: bool) -> Result { + pub fn decode_log_data(&self, log: &LogData, validate: bool) -> Result { self.decode_log_parts(log.topics().iter().copied(), &log.data, validate) } @@ -130,12 +133,55 @@ impl DynSolEvent { /// A decoded dynamic ABI event. #[derive(Clone, Debug, PartialEq)] pub struct DecodedEvent { + /// The hashes event_signature (if any) + #[doc(alias = "topic_0")] + pub selector: Option, /// The indexed values, in order. pub indexed: Vec, /// The un-indexed values, in order. pub body: Vec, } +impl DecodedEvent { + /// True if anonymous. False if not. + pub const fn is_anonymous(&self) -> bool { + self.selector.is_none() + } + + /// Re-encode the event into a [`LogData`] + pub fn encode_log_data(&self) -> LogData { + debug_assert!( + self.indexed.len() + !self.is_anonymous() as usize <= 4, + "too many indexed values" + ); + + LogData::new_unchecked( + self.selector + .iter() + .copied() + .chain(self.indexed.iter().flat_map(DynSolValue::as_word).map(B256::from)) + .collect(), + DynSolValue::encode_seq(&self.body).into(), + ) + } + + /// Transform a [`Log`] containing this event into a [`Log`] containing + /// [`LogData`]. + pub fn encode_log(log: Log) -> Log { + Log { address: log.address, data: log.data.encode_log_data() } + } +} + +impl IntoLogData for DecodedEvent { + fn to_log_data(&self) -> LogData { + self.encode_log_data() + } + + fn into_log_data(self) -> LogData { + self.encode_log_data() + } +} + #[cfg(test)] mod test { use alloy_primitives::{address, b256, bytes, U256}; @@ -150,13 +196,13 @@ mod test { indexed: vec![], body: DynSolType::Tuple(vec![DynSolType::Uint(256)]), }; - event.decode_log(&log, true).unwrap(); + event.decode_log_data(&log, true).unwrap(); } #[test] fn it_decodes_logs_with_indexed_params() { let t0 = b256!("cf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7"); - let log = LogData::new_unchecked( + let log: LogData = LogData::new_unchecked( vec![t0, b256!("0000000000000000000000000000000000000000000000000000000000012321")], bytes!( " @@ -174,10 +220,13 @@ mod test { ])]), }; - let decoded = event.decode_log(&log, true).unwrap(); + let decoded = event.decode_log_data(&log, true).unwrap(); assert_eq!( decoded.indexed, vec![DynSolValue::Address(address!("0000000000000000000000000000000000012321"))] ); + + let encoded = decoded.encode_log_data(); + assert_eq!(encoded, log); } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 7df97dbc06..dd88ad7a40 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -50,7 +50,7 @@ mod common; pub use common::TxKind; mod log; -pub use log::{Log, LogData}; +pub use log::{IntoLogData, Log, LogData}; #[cfg(feature = "serde")] mod log_serde; diff --git a/crates/primitives/src/log.rs b/crates/primitives/src/log.rs index 5d91d3a960..84da4f993b 100644 --- a/crates/primitives/src/log.rs +++ b/crates/primitives/src/log.rs @@ -81,6 +81,26 @@ impl LogData { } } +/// Trait for an object that can be converted into a log data object. +pub trait IntoLogData { + /// Convert into a [`LogData`] object. + fn to_log_data(&self) -> LogData; + /// Consume and convert into a [`LogData`] object. + fn into_log_data(self) -> LogData; +} + +impl IntoLogData for LogData { + #[inline] + fn to_log_data(&self) -> LogData { + self.clone() + } + + #[inline] + fn into_log_data(self) -> LogData { + self + } +} + /// A log consists of an address, and some log data. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))] diff --git a/crates/sol-macro-expander/src/expand/contract.rs b/crates/sol-macro-expander/src/expand/contract.rs index 4790e3a845..8792cadee5 100644 --- a/crates/sol-macro-expander/src/expand/contract.rs +++ b/crates/sol-macro-expander/src/expand/contract.rs @@ -811,6 +811,29 @@ impl<'a> CallLikeExpander<'a> { } }); + let into_impl = { + let variants = events.iter().map(e_name); + let v2 = variants.clone(); + quote! { + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for #name { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + match self {#( + Self::#variants(inner) => + alloy_sol_types::private::IntoLogData::to_log_data(inner), + )*} + } + + fn into_log_data(self) -> alloy_sol_types::private::LogData { + match self {#( + Self::#v2(inner) => + alloy_sol_types::private::IntoLogData::into_log_data(inner), + )*} + } + } + } + }; + quote! { #def @@ -824,6 +847,8 @@ impl<'a> CallLikeExpander<'a> { #anon_impl } } + + #into_impl } } diff --git a/crates/sol-macro-expander/src/expand/event.rs b/crates/sol-macro-expander/src/expand/event.rs index 2b09461879..a07df2a851 100644 --- a/crates/sol-macro-expander/src/expand/event.rs +++ b/crates/sol-macro-expander/src/expand/event.rs @@ -196,12 +196,23 @@ pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result } } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for #name { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + + + #[automatically_derived] impl From<&#name> for alloy_sol_types::private::LogData { #[inline] fn from(this: &#name) -> alloy_sol_types::private::LogData { - let topics = alloy_sol_types::SolEvent::encode_topics(this).into_iter().map(|t| t.into()).collect(); - let data = alloy_sol_types::SolEvent::encode_data(this).into(); - alloy_sol_types::private::LogData::new_unchecked(topics, data) + alloy_sol_types::SolEvent::encode_log_data(this) } } diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index c9d6bfd9b3..fcdba2d83c 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -218,8 +218,8 @@ pub mod private { vec::Vec, }; pub use alloy_primitives::{ - bytes, keccak256, Address, Bytes, FixedBytes, Function, LogData, Signed, Uint, B256, I256, - U256, + bytes, keccak256, Address, Bytes, FixedBytes, Function, IntoLogData, LogData, Signed, Uint, + B256, I256, U256, }; pub use core::{ borrow::{Borrow, BorrowMut}, diff --git a/crates/sol-types/src/types/event/mod.rs b/crates/sol-types/src/types/event/mod.rs index 398d2d1a9c..1276654914 100644 --- a/crates/sol-types/src/types/event/mod.rs +++ b/crates/sol-types/src/types/event/mod.rs @@ -122,6 +122,20 @@ pub trait SolEvent: Sized { out } + /// Encode this event to a [`LogData`]. + fn encode_log_data(&self) -> LogData { + LogData::new_unchecked( + self.encode_topics().into_iter().map(Into::into).collect(), + self.encode_data().into(), + ) + } + + /// Transform ca [`Log`] containing this event into a [`Log`] containing + /// [`LogData`]. + fn encode_log(log: &Log) -> Log { + Log { address: log.address, data: log.data.encode_log_data() } + } + /// Decode the topics of this event from the given data. #[inline] fn decode_topics(topics: I) -> Result<::RustType>