alloy_primitives/log/
mod.rs

1use crate::{Address, Bloom, Bytes, B256};
2use alloc::vec::Vec;
3
4#[cfg(feature = "serde")]
5mod serde;
6
7/// Compute the logs bloom filter for the given logs.
8pub fn logs_bloom<'a>(logs: impl IntoIterator<Item = &'a Log>) -> Bloom {
9    let mut bloom = Bloom::ZERO;
10    for log in logs {
11        bloom.accrue_log(log);
12    }
13    bloom
14}
15
16/// An Ethereum event log object.
17#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
19#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
20pub struct LogData {
21    /// The indexed topic list.
22    topics: Vec<B256>,
23    /// The plain data.
24    pub data: Bytes,
25}
26
27impl LogData {
28    /// Creates a new log, without length-checking. This allows creation of
29    /// invalid logs. May be safely used when the length of the topic list is
30    /// known to be 4 or less.
31    #[inline]
32    pub const fn new_unchecked(topics: Vec<B256>, data: Bytes) -> Self {
33        Self { topics, data }
34    }
35
36    /// Creates a new log.
37    #[inline]
38    pub fn new(topics: Vec<B256>, data: Bytes) -> Option<Self> {
39        let this = Self::new_unchecked(topics, data);
40        this.is_valid().then_some(this)
41    }
42
43    /// Creates a new empty log.
44    #[inline]
45    pub const fn empty() -> Self {
46        Self { topics: Vec::new(), data: Bytes::new() }
47    }
48
49    /// True if valid, false otherwise.
50    #[inline]
51    pub fn is_valid(&self) -> bool {
52        self.topics.len() <= 4
53    }
54
55    /// Get the topic list.
56    #[inline]
57    pub fn topics(&self) -> &[B256] {
58        &self.topics
59    }
60
61    /// Get the topic list, mutably. This gives access to the internal
62    /// array, without allowing extension of that array.
63    #[inline]
64    pub fn topics_mut(&mut self) -> &mut [B256] {
65        &mut self.topics
66    }
67
68    /// Get a mutable reference to the topic list. This allows creation of
69    /// invalid logs.
70    #[inline]
71    pub fn topics_mut_unchecked(&mut self) -> &mut Vec<B256> {
72        &mut self.topics
73    }
74
75    /// Set the topic list, without length-checking. This allows creation of
76    /// invalid logs.
77    #[inline]
78    pub fn set_topics_unchecked(&mut self, topics: Vec<B256>) {
79        self.topics = topics;
80    }
81
82    /// Set the topic list, truncating to 4 topics.
83    #[inline]
84    pub fn set_topics_truncating(&mut self, mut topics: Vec<B256>) {
85        topics.truncate(4);
86        self.set_topics_unchecked(topics);
87    }
88
89    /// Consumes the log data, returning the topic list and the data.
90    #[inline]
91    pub fn split(self) -> (Vec<B256>, Bytes) {
92        (self.topics, self.data)
93    }
94}
95
96/// Trait for an object that can be converted into a log data object.
97pub trait IntoLogData {
98    /// Convert into a [`LogData`] object.
99    fn to_log_data(&self) -> LogData;
100    /// Consume and convert into a [`LogData`] object.
101    fn into_log_data(self) -> LogData;
102}
103
104impl IntoLogData for LogData {
105    #[inline]
106    fn to_log_data(&self) -> LogData {
107        self.clone()
108    }
109
110    #[inline]
111    fn into_log_data(self) -> LogData {
112        self
113    }
114}
115
116/// A log consists of an address, and some log data.
117#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
118#[cfg_attr(feature = "arbitrary", derive(derive_arbitrary::Arbitrary, proptest_derive::Arbitrary))]
119pub struct Log<T = LogData> {
120    /// The address which emitted this log.
121    pub address: Address,
122    /// The log data.
123    pub data: T,
124}
125
126impl<T> core::ops::Deref for Log<T> {
127    type Target = T;
128
129    #[inline]
130    fn deref(&self) -> &Self::Target {
131        &self.data
132    }
133}
134
135impl<T> core::ops::DerefMut for Log<T> {
136    #[inline]
137    fn deref_mut(&mut self) -> &mut Self::Target {
138        &mut self.data
139    }
140}
141
142impl<T> AsRef<Self> for Log<T> {
143    fn as_ref(&self) -> &Self {
144        self
145    }
146}
147
148impl Log {
149    /// Creates a new log.
150    #[inline]
151    pub fn new(address: Address, topics: Vec<B256>, data: Bytes) -> Option<Self> {
152        LogData::new(topics, data).map(|data| Self { address, data })
153    }
154
155    /// Creates a new log.
156    #[inline]
157    pub const fn new_unchecked(address: Address, topics: Vec<B256>, data: Bytes) -> Self {
158        Self { address, data: LogData::new_unchecked(topics, data) }
159    }
160
161    /// Creates a new empty log.
162    #[inline]
163    pub const fn empty() -> Self {
164        Self { address: Address::ZERO, data: LogData::empty() }
165    }
166}
167
168impl<T> Log<T>
169where
170    for<'a> &'a T: Into<LogData>,
171{
172    /// Creates a new log.
173    #[inline]
174    pub const fn new_from_event_unchecked(address: Address, data: T) -> Self {
175        Self { address, data }
176    }
177
178    /// Creates a new log from an deserialized event.
179    pub fn new_from_event(address: Address, data: T) -> Option<Self> {
180        let this = Self::new_from_event_unchecked(address, data);
181        (&this.data).into().is_valid().then_some(this)
182    }
183
184    /// Reserialize the data.
185    #[inline]
186    pub fn reserialize(&self) -> Log<LogData> {
187        Log { address: self.address, data: (&self.data).into() }
188    }
189}
190
191#[cfg(feature = "rlp")]
192impl alloy_rlp::Encodable for Log {
193    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
194        let payload_length =
195            self.address.length() + self.data.data.length() + self.data.topics.length();
196
197        alloy_rlp::Header { list: true, payload_length }.encode(out);
198        self.address.encode(out);
199        self.data.topics.encode(out);
200        self.data.data.encode(out);
201    }
202
203    fn length(&self) -> usize {
204        let payload_length =
205            self.address.length() + self.data.data.length() + self.data.topics.length();
206        payload_length + alloy_rlp::length_of_length(payload_length)
207    }
208}
209
210#[cfg(feature = "rlp")]
211impl<T> alloy_rlp::Encodable for Log<T>
212where
213    for<'a> &'a T: Into<LogData>,
214{
215    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
216        self.reserialize().encode(out)
217    }
218
219    fn length(&self) -> usize {
220        self.reserialize().length()
221    }
222}
223
224#[cfg(feature = "rlp")]
225impl alloy_rlp::Decodable for Log {
226    fn decode(buf: &mut &[u8]) -> Result<Self, alloy_rlp::Error> {
227        let h = alloy_rlp::Header::decode(buf)?;
228        let pre = buf.len();
229
230        let address = alloy_rlp::Decodable::decode(buf)?;
231        let topics = alloy_rlp::Decodable::decode(buf)?;
232        let data = alloy_rlp::Decodable::decode(buf)?;
233
234        if h.payload_length != pre - buf.len() {
235            return Err(alloy_rlp::Error::Custom("did not consume exact payload"));
236        }
237
238        Ok(Self { address, data: LogData { topics, data } })
239    }
240}
241
242#[cfg(feature = "rlp")]
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use alloy_rlp::{Decodable, Encodable};
247
248    #[test]
249    fn test_roundtrip_rlp_log_data() {
250        let log = Log::<LogData>::default();
251        let mut buf = Vec::<u8>::new();
252        log.encode(&mut buf);
253        assert_eq!(Log::decode(&mut &buf[..]).unwrap(), log);
254    }
255}