alloy_dyn_abi/dynamic/
event.rs1use crate::{DynSolType, DynSolValue, Error, Result};
2use alloc::vec::Vec;
3use alloy_primitives::{IntoLogData, Log, LogData, B256};
4
5#[derive(Clone, Debug, PartialEq)]
10pub struct DynSolEvent {
11 pub(crate) topic_0: Option<B256>,
13 pub(crate) indexed: Vec<DynSolType>,
15 pub(crate) body: DynSolType,
17}
18
19impl DynSolEvent {
20 pub const fn new_unchecked(
23 topic_0: Option<B256>,
24 indexed: Vec<DynSolType>,
25 body: DynSolType,
26 ) -> Self {
27 Self { topic_0, indexed, body }
28 }
29
30 pub fn new(topic_0: Option<B256>, indexed: Vec<DynSolType>, body: DynSolType) -> Option<Self> {
35 let topics = indexed.len() + topic_0.is_some() as usize;
36 if topics > 4 || body.as_tuple().is_none() {
37 return None;
38 }
39 Some(Self::new_unchecked(topic_0, indexed, body))
40 }
41
42 pub const fn is_anonymous(&self) -> bool {
44 self.topic_0.is_none()
45 }
46
47 pub fn decode_log_parts<I>(
49 &self,
50 topics: I,
51 data: &[u8],
52 validate: bool,
53 ) -> Result<DecodedEvent>
54 where
55 I: IntoIterator<Item = B256>,
56 {
57 let mut topics = topics.into_iter();
58 let num_topics = self.indexed.len() + !self.is_anonymous() as usize;
59 if validate {
60 match topics.size_hint() {
61 (n, Some(m)) if n == m && n != num_topics => {
62 return Err(Error::TopicLengthMismatch { expected: num_topics, actual: n })
63 }
64 _ => {}
65 }
66 }
67
68 if !self.is_anonymous() {
70 let t = topics.next();
71 if validate {
73 match t {
74 Some(sig) => {
75 let expected = self.topic_0.expect("not anonymous");
76 if sig != expected {
77 return Err(Error::EventSignatureMismatch { expected, actual: sig });
78 }
79 }
80 None => {
81 return Err(Error::TopicLengthMismatch { expected: num_topics, actual: 0 })
82 }
83 }
84 }
85 }
86
87 let indexed = self
88 .indexed
89 .iter()
90 .zip(topics.by_ref().take(self.indexed.len()))
91 .map(|(ty, topic)| {
92 let value = ty.decode_event_topic(topic);
93 Ok(value)
94 })
95 .collect::<Result<_>>()?;
96
97 let body = self.body.abi_decode_sequence(data)?.into_fixed_seq().expect("body is a tuple");
98
99 if validate {
100 let remaining = topics.count();
101 if remaining > 0 {
102 return Err(Error::TopicLengthMismatch {
103 expected: num_topics,
104 actual: num_topics + remaining,
105 });
106 }
107 }
108
109 Ok(DecodedEvent { selector: self.topic_0, indexed, body })
110 }
111
112 pub fn decode_log_data(&self, log: &LogData, validate: bool) -> Result<DecodedEvent> {
114 self.decode_log_parts(log.topics().iter().copied(), &log.data, validate)
115 }
116
117 pub const fn topic_0(&self) -> Option<B256> {
119 self.topic_0
120 }
121
122 pub fn indexed(&self) -> &[DynSolType] {
124 &self.indexed
125 }
126
127 pub fn body(&self) -> &[DynSolType] {
129 self.body.as_tuple().expect("body is a tuple")
130 }
131}
132
133#[derive(Clone, Debug, PartialEq)]
135pub struct DecodedEvent {
136 #[doc(alias = "topic_0")]
138 pub selector: Option<B256>,
139 pub indexed: Vec<DynSolValue>,
141 pub body: Vec<DynSolValue>,
143}
144
145impl DecodedEvent {
146 pub const fn is_anonymous(&self) -> bool {
148 self.selector.is_none()
149 }
150
151 pub fn encode_log_data(&self) -> LogData {
153 debug_assert!(
154 self.indexed.len() + !self.is_anonymous() as usize <= 4,
155 "too many indexed values"
156 );
157
158 LogData::new_unchecked(
159 self.selector
160 .iter()
161 .copied()
162 .chain(self.indexed.iter().flat_map(DynSolValue::as_word))
163 .collect(),
164 DynSolValue::encode_seq(&self.body).into(),
165 )
166 }
167
168 pub fn encode_log(log: Log<Self>) -> Log<LogData> {
171 Log { address: log.address, data: log.data.encode_log_data() }
172 }
173}
174
175impl IntoLogData for DecodedEvent {
176 fn to_log_data(&self) -> LogData {
177 self.encode_log_data()
178 }
179
180 fn into_log_data(self) -> LogData {
181 self.encode_log_data()
182 }
183}
184
185#[cfg(test)]
186mod test {
187 use super::*;
188 use alloy_primitives::{address, b256, bytes, U256};
189
190 #[test]
191 fn it_decodes_a_simple_log() {
192 let log = LogData::new_unchecked(vec![], U256::ZERO.to_be_bytes_vec().into());
193 let event = DynSolEvent {
194 topic_0: None,
195 indexed: vec![],
196 body: DynSolType::Tuple(vec![DynSolType::Uint(256)]),
197 };
198 event.decode_log_data(&log, true).unwrap();
199 }
200
201 #[test]
202 fn it_decodes_logs_with_indexed_params() {
203 let t0 = b256!("0xcf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7");
204 let log: LogData = LogData::new_unchecked(
205 vec![t0, b256!("0x0000000000000000000000000000000000000000000000000000000000012321")],
206 bytes!(
207 "
208 0000000000000000000000000000000000000000000000000000000000012345
209 0000000000000000000000000000000000000000000000000000000000054321
210 "
211 ),
212 );
213 let event = DynSolEvent {
214 topic_0: Some(t0),
215 indexed: vec![DynSolType::Address],
216 body: DynSolType::Tuple(vec![DynSolType::Tuple(vec![
217 DynSolType::Address,
218 DynSolType::Address,
219 ])]),
220 };
221
222 let decoded = event.decode_log_data(&log, true).unwrap();
223 assert_eq!(
224 decoded.indexed,
225 vec![DynSolValue::Address(address!("0x0000000000000000000000000000000000012321"))]
226 );
227
228 let encoded = decoded.encode_log_data();
229 assert_eq!(encoded, log);
230 }
231}