alloy_primitives/log/
mod.rs

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