1use alloy_primitives::{Address, BlockHash, LogData, TxHash, B256};
2
3#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
7#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
8pub struct Log<T = LogData> {
9 #[cfg_attr(feature = "serde", serde(flatten))]
10 pub inner: alloy_primitives::Log<T>,
12 pub block_hash: Option<BlockHash>,
14 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
16 pub block_number: Option<u64>,
17 #[cfg_attr(
21 feature = "serde",
22 serde(
23 skip_serializing_if = "Option::is_none",
24 with = "alloy_serde::quantity::opt",
25 default
26 )
27 )]
28 pub block_timestamp: Option<u64>,
29 #[doc(alias = "tx_hash")]
31 pub transaction_hash: Option<TxHash>,
32 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
34 #[doc(alias = "tx_index")]
35 pub transaction_index: Option<u64>,
36 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
38 pub log_index: Option<u64>,
39 #[cfg_attr(feature = "serde", serde(default))]
41 pub removed: bool,
42}
43
44impl<T> Log<T> {
45 pub const fn address(&self) -> Address {
47 self.inner.address
48 }
49
50 pub const fn data(&self) -> &T {
52 &self.inner.data
53 }
54
55 pub fn into_inner(self) -> alloy_primitives::Log<T> {
57 self.inner
58 }
59}
60
61impl Log<LogData> {
62 pub fn topics(&self) -> &[B256] {
64 self.inner.topics()
65 }
66
67 #[doc(alias = "event_signature")]
69 pub fn topic0(&self) -> Option<&B256> {
70 self.inner.topics().first()
71 }
72
73 pub fn topics_mut(&mut self) -> &mut [B256] {
77 self.inner.data.topics_mut()
78 }
79
80 pub fn log_decode<T: alloy_sol_types::SolEvent>(&self) -> alloy_sol_types::Result<Log<T>> {
82 let decoded = T::decode_log(&self.inner)?;
83 Ok(Log {
84 inner: decoded,
85 block_hash: self.block_hash,
86 block_number: self.block_number,
87 block_timestamp: self.block_timestamp,
88 transaction_hash: self.transaction_hash,
89 transaction_index: self.transaction_index,
90 log_index: self.log_index,
91 removed: self.removed,
92 })
93 }
94}
95
96impl<T> alloy_rlp::Encodable for Log<T>
97where
98 for<'a> &'a T: Into<LogData>,
99{
100 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
101 self.reserialize_inner().encode(out)
102 }
103
104 fn length(&self) -> usize {
105 self.reserialize_inner().length()
106 }
107}
108
109impl<T> Log<T>
110where
111 for<'a> &'a T: Into<LogData>,
112{
113 pub fn reserialize_inner(&self) -> alloy_primitives::Log {
115 alloy_primitives::Log { address: self.inner.address, data: (&self.inner.data).into() }
116 }
117
118 pub fn reserialize(&self) -> Log<LogData> {
122 Log {
123 inner: self.reserialize_inner(),
124 block_hash: self.block_hash,
125 block_number: self.block_number,
126 block_timestamp: self.block_timestamp,
127 transaction_hash: self.transaction_hash,
128 transaction_index: self.transaction_index,
129 log_index: self.log_index,
130 removed: self.removed,
131 }
132 }
133}
134
135impl<T> AsRef<alloy_primitives::Log<T>> for Log<T> {
136 fn as_ref(&self) -> &alloy_primitives::Log<T> {
137 &self.inner
138 }
139}
140
141impl<T> AsMut<alloy_primitives::Log<T>> for Log<T> {
142 fn as_mut(&mut self) -> &mut alloy_primitives::Log<T> {
143 &mut self.inner
144 }
145}
146
147impl<T> AsRef<T> for Log<T> {
148 fn as_ref(&self) -> &T {
149 &self.inner.data
150 }
151}
152
153impl<T> AsMut<T> for Log<T> {
154 fn as_mut(&mut self) -> &mut T {
155 &mut self.inner.data
156 }
157}
158
159impl<L> From<Log<L>> for alloy_primitives::Log<L> {
160 fn from(value: Log<L>) -> Self {
161 value.into_inner()
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use alloy_consensus::{Receipt, ReceiptWithBloom, TxReceipt};
169 use alloy_primitives::{Address, Bytes};
170 use arbitrary::Arbitrary;
171 use rand::Rng;
172 use similar_asserts::assert_eq;
173
174 const fn assert_tx_receipt<T: TxReceipt>() {}
175
176 #[test]
177 const fn assert_receipt() {
178 assert_tx_receipt::<ReceiptWithBloom<Receipt<Log>>>();
179 }
180
181 #[test]
182 fn log_arbitrary() {
183 let mut bytes = [0u8; 1024];
184 rand::thread_rng().fill(bytes.as_mut_slice());
185
186 let _: Log = Log::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
187 }
188
189 #[test]
190 #[cfg(feature = "serde")]
191 fn serde_log() {
192 let mut log = Log {
193 inner: alloy_primitives::Log {
194 address: Address::with_last_byte(0x69),
195 data: alloy_primitives::LogData::new_unchecked(
196 vec![B256::with_last_byte(0x69)],
197 Bytes::from_static(&[0x69]),
198 ),
199 },
200 block_hash: Some(B256::with_last_byte(0x69)),
201 block_number: Some(0x69),
202 block_timestamp: None,
203 transaction_hash: Some(B256::with_last_byte(0x69)),
204 transaction_index: Some(0x69),
205 log_index: Some(0x69),
206 removed: false,
207 };
208 let serialized = serde_json::to_string(&log).unwrap();
209 assert_eq!(
210 serialized,
211 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
212 );
213
214 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
215 assert_eq!(log, deserialized);
216
217 log.block_timestamp = Some(0x69);
218 let serialized = serde_json::to_string(&log).unwrap();
219 assert_eq!(
220 serialized,
221 r#"{"address":"0x0000000000000000000000000000000000000069","topics":["0x0000000000000000000000000000000000000000000000000000000000000069"],"data":"0x69","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000069","blockNumber":"0x69","blockTimestamp":"0x69","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000069","transactionIndex":"0x69","logIndex":"0x69","removed":false}"#
222 );
223
224 let deserialized: Log = serde_json::from_str(&serialized).unwrap();
225 assert_eq!(log, deserialized);
226 }
227}