alloy_rpc_types_eth/
log.rs

1use alloy_primitives::{Address, BlockHash, LogData, TxHash, B256};
2
3/// Ethereum Log emitted by a transaction
4#[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    /// Consensus log object
11    pub inner: alloy_primitives::Log<T>,
12    /// Hash of the block the transaction that emitted this log was mined in
13    pub block_hash: Option<BlockHash>,
14    /// Number of the block the transaction that emitted this log was mined in
15    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
16    pub block_number: Option<u64>,
17    /// The timestamp of the block as proposed in:
18    /// <https://ethereum-magicians.org/t/proposal-for-adding-blocktimestamp-to-logs-object-returned-by-eth-getlogs-and-related-requests>
19    /// <https://github.com/ethereum/execution-apis/issues/295>
20    #[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    /// Transaction Hash
30    #[doc(alias = "tx_hash")]
31    pub transaction_hash: Option<TxHash>,
32    /// Index of the Transaction in the block
33    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
34    #[doc(alias = "tx_index")]
35    pub transaction_index: Option<u64>,
36    /// Log Index in Block
37    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity::opt"))]
38    pub log_index: Option<u64>,
39    /// Geth Compatibility Field: whether this log was removed
40    #[cfg_attr(feature = "serde", serde(default))]
41    pub removed: bool,
42}
43
44impl<T> Log<T> {
45    /// Getter for the address field. Shortcut for `log.inner.address`.
46    pub const fn address(&self) -> Address {
47        self.inner.address
48    }
49
50    /// Getter for the data field. Shortcut for `log.inner.data`.
51    pub const fn data(&self) -> &T {
52        &self.inner.data
53    }
54
55    /// Consumes the type and returns the wrapped [`alloy_primitives::Log`]
56    pub fn into_inner(self) -> alloy_primitives::Log<T> {
57        self.inner
58    }
59}
60
61impl Log<LogData> {
62    /// Getter for the topics field. Shortcut for `log.inner.topics()`.
63    pub fn topics(&self) -> &[B256] {
64        self.inner.topics()
65    }
66
67    /// Getter for the topic0 field.
68    #[doc(alias = "event_signature")]
69    pub fn topic0(&self) -> Option<&B256> {
70        self.inner.topics().first()
71    }
72
73    /// Get the topic list, mutably. This gives access to the internal
74    /// array, without allowing extension of that array. Shortcut for
75    /// [`LogData::topics_mut`]
76    pub fn topics_mut(&mut self) -> &mut [B256] {
77        self.inner.data.topics_mut()
78    }
79
80    /// Decode the log data into a typed log.
81    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    /// Reserialize the inner data, returning an [`alloy_primitives::Log`].
114    pub fn reserialize_inner(&self) -> alloy_primitives::Log {
115        alloy_primitives::Log { address: self.inner.address, data: (&self.inner.data).into() }
116    }
117
118    /// Reserialize the data, returning a new `Log` object wrapping an
119    /// [`alloy_primitives::Log`]. this copies the log metadata, preserving
120    /// the original object.
121    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}