alloy_primitives/log/
mod.rs1use crate::{Address, Bytes, B256};
2use alloc::vec::Vec;
3
4#[cfg(feature = "serde")]
5mod serde;
6
7#[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 topics: Vec<B256>,
14 pub data: Bytes,
16}
17
18impl LogData {
19 #[inline]
23 pub const fn new_unchecked(topics: Vec<B256>, data: Bytes) -> Self {
24 Self { topics, data }
25 }
26
27 #[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 #[inline]
36 pub const fn empty() -> Self {
37 Self { topics: Vec::new(), data: Bytes::new() }
38 }
39
40 #[inline]
42 pub fn is_valid(&self) -> bool {
43 self.topics.len() <= 4
44 }
45
46 #[inline]
48 pub fn topics(&self) -> &[B256] {
49 &self.topics
50 }
51
52 #[inline]
55 pub fn topics_mut(&mut self) -> &mut [B256] {
56 &mut self.topics
57 }
58
59 #[inline]
62 pub fn topics_mut_unchecked(&mut self) -> &mut Vec<B256> {
63 &mut self.topics
64 }
65
66 #[inline]
69 pub fn set_topics_unchecked(&mut self, topics: Vec<B256>) {
70 self.topics = topics;
71 }
72
73 #[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 #[inline]
82 pub fn split(self) -> (Vec<B256>, Bytes) {
83 (self.topics, self.data)
84 }
85}
86
87pub trait IntoLogData {
89 fn to_log_data(&self) -> LogData;
91 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#[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 pub address: Address,
113 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 #[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 #[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 #[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 #[inline]
152 pub const fn new_from_event_unchecked(address: Address, data: T) -> Self {
153 Self { address, data }
154 }
155
156 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 #[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}