alloy_primitives/log/
mod.rs1use crate::{Address, Bloom, Bytes, B256};
2use alloc::vec::Vec;
3
4#[cfg(feature = "serde")]
5mod serde;
6
7pub 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#[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 topics: Vec<B256>,
23 pub data: Bytes,
25}
26
27impl LogData {
28 #[inline]
32 pub const fn new_unchecked(topics: Vec<B256>, data: Bytes) -> Self {
33 Self { topics, data }
34 }
35
36 #[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 #[inline]
45 pub const fn empty() -> Self {
46 Self { topics: Vec::new(), data: Bytes::new() }
47 }
48
49 #[inline]
51 pub fn is_valid(&self) -> bool {
52 self.topics.len() <= 4
53 }
54
55 #[inline]
57 pub fn topics(&self) -> &[B256] {
58 &self.topics
59 }
60
61 #[inline]
64 pub fn topics_mut(&mut self) -> &mut [B256] {
65 &mut self.topics
66 }
67
68 #[inline]
71 pub fn topics_mut_unchecked(&mut self) -> &mut Vec<B256> {
72 &mut self.topics
73 }
74
75 #[inline]
78 pub fn set_topics_unchecked(&mut self, topics: Vec<B256>) {
79 self.topics = topics;
80 }
81
82 #[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 #[inline]
91 pub fn split(self) -> (Vec<B256>, Bytes) {
92 (self.topics, self.data)
93 }
94}
95
96pub trait IntoLogData {
98 fn to_log_data(&self) -> LogData;
100 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#[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 pub address: Address,
122 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 #[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 #[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 #[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 #[inline]
174 pub const fn new_from_event_unchecked(address: Address, data: T) -> Self {
175 Self { address, data }
176 }
177
178 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 #[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}