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))]
45 pub removed: bool,
46}
47
48impl<T> Log<T> {
49 pub const fn address(&self) -> Address {
51 self.inner.address
52 }
53
54 pub const fn data(&self) -> &T {
56 &self.inner.data
57 }
58
59 pub fn into_inner(self) -> alloy_primitives::Log<T> {
61 self.inner
62 }
63}
64
65impl Log<LogData> {
66 pub fn topics(&self) -> &[B256] {
68 self.inner.topics()
69 }
70
71 #[doc(alias = "event_signature")]
73 pub fn topic0(&self) -> Option<&B256> {
74 self.inner.topics().first()
75 }
76
77 pub fn topics_mut(&mut self) -> &mut [B256] {
81 self.inner.data.topics_mut()
82 }
83
84 pub fn log_decode<T: alloy_sol_types::SolEvent>(&self) -> alloy_sol_types::Result<Log<T>> {
86 let decoded = T::decode_log(&self.inner)?;
87 Ok(Log {
88 inner: decoded,
89 block_hash: self.block_hash,
90 block_number: self.block_number,
91 block_timestamp: self.block_timestamp,
92 transaction_hash: self.transaction_hash,
93 transaction_index: self.transaction_index,
94 log_index: self.log_index,
95 removed: self.removed,
96 })
97 }
98
99 pub fn log_decode_validate<T: alloy_sol_types::SolEvent>(
101 &self,
102 ) -> alloy_sol_types::Result<Log<T>> {
103 let decoded = T::decode_log_validate(&self.inner)?;
104 Ok(Log {
105 inner: decoded,
106 block_hash: self.block_hash,
107 block_number: self.block_number,
108 block_timestamp: self.block_timestamp,
109 transaction_hash: self.transaction_hash,
110 transaction_index: self.transaction_index,
111 log_index: self.log_index,
112 removed: self.removed,
113 })
114 }
115
116 pub fn collect_for_receipt<I, T>(
134 previous_log_count: usize,
135 meta: TransactionMeta,
136 logs: I,
137 ) -> Vec<Log<T>>
138 where
139 I: IntoIterator<Item = alloy_primitives::Log<T>>,
140 {
141 logs.into_iter()
142 .enumerate()
143 .map(|(tx_log_idx, log)| Log {
144 inner: log,
145 block_hash: Some(meta.block_hash),
146 block_number: Some(meta.block_number),
147 block_timestamp: Some(meta.timestamp),
148 transaction_hash: Some(meta.tx_hash),
149 transaction_index: Some(meta.index),
150 log_index: Some((previous_log_count + tx_log_idx) as u64),
151 removed: false,
152 })
153 .collect()
154 }
155}
156
157impl<T> alloy_rlp::Encodable for Log<T>
158where
159 for<'a> &'a T: Into<LogData>,
160{
161 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
162 self.reserialize_inner().encode(out)
163 }
164
165 fn length(&self) -> usize {
166 self.reserialize_inner().length()
167 }
168}
169
170impl<T> Log<T>
171where
172 for<'a> &'a T: Into<LogData>,
173{
174 pub fn reserialize_inner(&self) -> alloy_primitives::Log {
176 alloy_primitives::Log { address: self.inner.address, data: (&self.inner.data).into() }
177 }
178
179 pub fn reserialize(&self) -> Log<LogData> {
183 Log {
184 inner: self.reserialize_inner(),
185 block_hash: self.block_hash,
186 block_number: self.block_number,
187 block_timestamp: self.block_timestamp,
188 transaction_hash: self.transaction_hash,
189 transaction_index: self.transaction_index,
190 log_index: self.log_index,
191 removed: self.removed,
192 }
193 }
194}
195
196impl<T> AsRef<alloy_primitives::Log<T>> for Log<T> {
197 fn as_ref(&self) -> &alloy_primitives::Log<T> {
198 &self.inner
199 }
200}
201
202impl<T> AsMut<alloy_primitives::Log<T>> for Log<T> {
203 fn as_mut(&mut self) -> &mut alloy_primitives::Log<T> {
204 &mut self.inner
205 }
206}
207
208impl<T> AsRef<T> for Log<T> {
209 fn as_ref(&self) -> &T {
210 &self.inner.data
211 }
212}
213
214impl<T> AsMut<T> for Log<T> {
215 fn as_mut(&mut self) -> &mut T {
216 &mut self.inner.data
217 }
218}
219
220impl<L> From<Log<L>> for alloy_primitives::Log<L> {
221 fn from(value: Log<L>) -> Self {
222 value.into_inner()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt};
230 use alloy_primitives::{Address, Bytes};
231 use arbitrary::Arbitrary;
232 use rand::Rng;
233 use similar_asserts::assert_eq;
234
235 const fn assert_tx_receipt<T: TxReceipt>() {}
236
237 #[test]
238 const fn assert_receipt() {
239 assert_tx_receipt::<ReceiptWithBloom<Receipt<Log>>>();
240 }
241
242 #[test]
243 fn log_arbitrary() {
244 let mut bytes = [0u8; 1024];
245 rand::thread_rng().fill(bytes.as_mut_slice());
246
247 let _: Log = Log::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
248 }
249
250 #[test]
251 #[cfg(feature = "serde")]
252 fn serde_log() {
253 let mut log = Log {
254 inner: alloy_primitives::Log {
255 address: Address::with_last_byte(0x69),
256 data: alloy_primitives::LogData::new_unchecked(
257 vec![B256::with_last_byte(0x69)],
258 Bytes::from_static(&[0x69]),
259 ),
260 },
261 block_hash: Some(B256::with_last_byte(0x69)),
262 block_number: Some(0x69),
263 block_timestamp: None,
264 transaction_hash: Some(B256::with_last_byte(0x69)),
265 transaction_index: Some(0x69),
266 log_index: Some(0x69),
267 removed: false,
268 };
269 let serialized = serde_json::to_string(&log).unwrap();
270 assert_eq!(
271 serialized,
272 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
273 );
274
275 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
276 assert_eq!(log, deserialized);
277
278 log.block_timestamp = Some(0x69);
279 let serialized = serde_json::to_string(&log).unwrap();
280 assert_eq!(
281 serialized,
282 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","blockTimestamp":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
283 );
284
285 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
286 assert_eq!(log, deserialized);
287 }
288}