1use alloc::vec::Vec;
2use alloy_consensus::transaction::TransactionMeta;
3use alloy_primitives::{Address, BlockHash, LogData, TxHash, B256};
4
5#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
9#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
10pub struct Log<T = LogData> {
11 #[cfg_attr(feature = "serde", serde(flatten))]
12 pub inner: alloy_primitives::Log<T>,
14 pub block_hash: Option<BlockHash>,
16 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
18 pub block_number: Option<u64>,
19 #[cfg_attr(
23 feature = "serde",
24 serde(
25 skip_serializing_if = "Option::is_none",
26 with = "alloy_serde::quantity::opt",
27 default
28 )
29 )]
30 pub block_timestamp: Option<u64>,
31 #[doc(alias = "tx_hash")]
33 pub transaction_hash: Option<TxHash>,
34 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
36 #[doc(alias = "tx_index")]
37 pub transaction_index: Option<u64>,
38 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
40 pub log_index: Option<u64>,
41 #[cfg_attr(feature = "serde", serde(default))]
43 pub removed: bool,
44}
45
46impl<T> Log<T> {
47 pub const fn address(&self) -> Address {
49 self.inner.address
50 }
51
52 pub const fn data(&self) -> &T {
54 &self.inner.data
55 }
56
57 pub fn into_inner(self) -> alloy_primitives::Log<T> {
59 self.inner
60 }
61}
62
63impl Log<LogData> {
64 pub fn topics(&self) -> &[B256] {
66 self.inner.topics()
67 }
68
69 #[doc(alias = "event_signature")]
71 pub fn topic0(&self) -> Option<&B256> {
72 self.inner.topics().first()
73 }
74
75 pub fn topics_mut(&mut self) -> &mut [B256] {
79 self.inner.data.topics_mut()
80 }
81
82 pub fn log_decode<T: alloy_sol_types::SolEvent>(&self) -> alloy_sol_types::Result<Log<T>> {
84 let decoded = T::decode_log(&self.inner)?;
85 Ok(Log {
86 inner: decoded,
87 block_hash: self.block_hash,
88 block_number: self.block_number,
89 block_timestamp: self.block_timestamp,
90 transaction_hash: self.transaction_hash,
91 transaction_index: self.transaction_index,
92 log_index: self.log_index,
93 removed: self.removed,
94 })
95 }
96
97 pub fn log_decode_validate<T: alloy_sol_types::SolEvent>(
99 &self,
100 ) -> alloy_sol_types::Result<Log<T>> {
101 let decoded = T::decode_log_validate(&self.inner)?;
102 Ok(Log {
103 inner: decoded,
104 block_hash: self.block_hash,
105 block_number: self.block_number,
106 block_timestamp: self.block_timestamp,
107 transaction_hash: self.transaction_hash,
108 transaction_index: self.transaction_index,
109 log_index: self.log_index,
110 removed: self.removed,
111 })
112 }
113
114 pub fn collect_for_receipt<I, T>(
132 previous_log_count: usize,
133 meta: TransactionMeta,
134 logs: I,
135 ) -> Vec<Log<T>>
136 where
137 I: IntoIterator<Item = alloy_primitives::Log<T>>,
138 {
139 logs.into_iter()
140 .enumerate()
141 .map(|(tx_log_idx, log)| Log {
142 inner: log,
143 block_hash: Some(meta.block_hash),
144 block_number: Some(meta.block_number),
145 block_timestamp: Some(meta.timestamp),
146 transaction_hash: Some(meta.tx_hash),
147 transaction_index: Some(meta.index),
148 log_index: Some((previous_log_count + tx_log_idx) as u64),
149 removed: false,
150 })
151 .collect()
152 }
153}
154
155impl<T> alloy_rlp::Encodable for Log<T>
156where
157 for<'a> &'a T: Into<LogData>,
158{
159 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
160 self.reserialize_inner().encode(out)
161 }
162
163 fn length(&self) -> usize {
164 self.reserialize_inner().length()
165 }
166}
167
168impl<T> Log<T>
169where
170 for<'a> &'a T: Into<LogData>,
171{
172 pub fn reserialize_inner(&self) -> alloy_primitives::Log {
174 alloy_primitives::Log { address: self.inner.address, data: (&self.inner.data).into() }
175 }
176
177 pub fn reserialize(&self) -> Log<LogData> {
181 Log {
182 inner: self.reserialize_inner(),
183 block_hash: self.block_hash,
184 block_number: self.block_number,
185 block_timestamp: self.block_timestamp,
186 transaction_hash: self.transaction_hash,
187 transaction_index: self.transaction_index,
188 log_index: self.log_index,
189 removed: self.removed,
190 }
191 }
192}
193
194impl<T> AsRef<alloy_primitives::Log<T>> for Log<T> {
195 fn as_ref(&self) -> &alloy_primitives::Log<T> {
196 &self.inner
197 }
198}
199
200impl<T> AsMut<alloy_primitives::Log<T>> for Log<T> {
201 fn as_mut(&mut self) -> &mut alloy_primitives::Log<T> {
202 &mut self.inner
203 }
204}
205
206impl<T> AsRef<T> for Log<T> {
207 fn as_ref(&self) -> &T {
208 &self.inner.data
209 }
210}
211
212impl<T> AsMut<T> for Log<T> {
213 fn as_mut(&mut self) -> &mut T {
214 &mut self.inner.data
215 }
216}
217
218impl<L> From<Log<L>> for alloy_primitives::Log<L> {
219 fn from(value: Log<L>) -> Self {
220 value.into_inner()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt};
228 use alloy_primitives::{Address, Bytes};
229 use arbitrary::Arbitrary;
230 use rand::Rng;
231 use similar_asserts::assert_eq;
232
233 const fn assert_tx_receipt<T: TxReceipt>() {}
234
235 #[test]
236 const fn assert_receipt() {
237 assert_tx_receipt::<ReceiptWithBloom<Receipt<Log>>>();
238 }
239
240 #[test]
241 fn log_arbitrary() {
242 let mut bytes = [0u8; 1024];
243 rand::thread_rng().fill(bytes.as_mut_slice());
244
245 let _: Log = Log::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
246 }
247
248 #[test]
249 #[cfg(feature = "serde")]
250 fn serde_log() {
251 let mut log = Log {
252 inner: alloy_primitives::Log {
253 address: Address::with_last_byte(0x69),
254 data: alloy_primitives::LogData::new_unchecked(
255 vec![B256::with_last_byte(0x69)],
256 Bytes::from_static(&[0x69]),
257 ),
258 },
259 block_hash: Some(B256::with_last_byte(0x69)),
260 block_number: Some(0x69),
261 block_timestamp: None,
262 transaction_hash: Some(B256::with_last_byte(0x69)),
263 transaction_index: Some(0x69),
264 log_index: Some(0x69),
265 removed: false,
266 };
267 let serialized = serde_json::to_string(&log).unwrap();
268 assert_eq!(
269 serialized,
270 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
271 );
272
273 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
274 assert_eq!(log, deserialized);
275
276 log.block_timestamp = Some(0x69);
277 let serialized = serde_json::to_string(&log).unwrap();
278 assert_eq!(
279 serialized,
280 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","blockTimestamp":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
281 );
282
283 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
284 assert_eq!(log, deserialized);
285 }
286}