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>(&self, topics: I, data: &[u8]) -> Result<DecodedEvent>
49 where
50 I: IntoIterator<Item = B256>,
51 {
52 let mut topics = topics.into_iter();
53 let num_topics = self.indexed.len() + !self.is_anonymous() as usize;
54
55 match topics.size_hint() {
56 (n, Some(m)) if n == m && n != num_topics => {
57 return Err(Error::TopicLengthMismatch { expected: num_topics, actual: n })
58 }
59 _ => {}
60 }
61
62 if !self.is_anonymous() {
64 let t = topics.next();
65 match t {
66 Some(sig) => {
67 let expected = self.topic_0.expect("not anonymous");
68 if sig != expected {
69 return Err(Error::EventSignatureMismatch { expected, actual: sig });
70 }
71 }
72 None => return Err(Error::TopicLengthMismatch { expected: num_topics, actual: 0 }),
73 }
74 }
75
76 let indexed = self
77 .indexed
78 .iter()
79 .zip(topics.by_ref().take(self.indexed.len()))
80 .map(|(ty, topic)| {
81 let value = ty.decode_event_topic(topic);
82 Ok(value)
83 })
84 .collect::<Result<_>>()?;
85
86 let body = self.body.abi_decode_sequence(data)?.into_fixed_seq().expect("body is a tuple");
87
88 let remaining = topics.count();
89 if remaining > 0 {
90 return Err(Error::TopicLengthMismatch {
91 expected: num_topics,
92 actual: num_topics + remaining,
93 });
94 }
95
96 Ok(DecodedEvent { selector: self.topic_0, indexed, body })
97 }
98
99 pub fn decode_log_data(&self, log: &LogData) -> Result<DecodedEvent> {
101 self.decode_log_parts(log.topics().iter().copied(), &log.data)
102 }
103
104 pub const fn topic_0(&self) -> Option<B256> {
106 self.topic_0
107 }
108
109 pub fn indexed(&self) -> &[DynSolType] {
111 &self.indexed
112 }
113
114 pub fn body(&self) -> &[DynSolType] {
116 self.body.as_tuple().expect("body is a tuple")
117 }
118}
119
120#[derive(Clone, Debug, PartialEq)]
122pub struct DecodedEvent {
123 #[doc(alias = "topic_0")]
125 pub selector: Option<B256>,
126 pub indexed: Vec<DynSolValue>,
128 pub body: Vec<DynSolValue>,
130}
131
132impl DecodedEvent {
133 pub const fn is_anonymous(&self) -> bool {
135 self.selector.is_none()
136 }
137
138 pub fn encode_log_data(&self) -> LogData {
140 debug_assert!(
141 self.indexed.len() + !self.is_anonymous() as usize <= 4,
142 "too many indexed values"
143 );
144
145 LogData::new_unchecked(
146 self.selector
147 .iter()
148 .copied()
149 .chain(self.indexed.iter().flat_map(DynSolValue::as_word))
150 .collect(),
151 DynSolValue::encode_seq(&self.body).into(),
152 )
153 }
154
155 pub fn encode_log(log: Log<Self>) -> Log<LogData> {
158 Log { address: log.address, data: log.data.encode_log_data() }
159 }
160}
161
162impl IntoLogData for DecodedEvent {
163 fn to_log_data(&self) -> LogData {
164 self.encode_log_data()
165 }
166
167 fn into_log_data(self) -> LogData {
168 self.encode_log_data()
169 }
170}
171
172#[cfg(test)]
173mod test {
174 use super::*;
175 use alloy_primitives::{address, b256, bytes, U256};
176
177 #[test]
178 fn it_decodes_a_simple_log() {
179 let log = LogData::new_unchecked(vec![], U256::ZERO.to_be_bytes_vec().into());
180 let event = DynSolEvent {
181 topic_0: None,
182 indexed: vec![],
183 body: DynSolType::Tuple(vec![DynSolType::Uint(256)]),
184 };
185 event.decode_log_data(&log).unwrap();
186 }
187
188 #[test]
189 fn it_decodes_logs_with_indexed_params() {
190 let t0 = b256!("0xcf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7");
191 let log: LogData = LogData::new_unchecked(
192 vec![t0, b256!("0x0000000000000000000000000000000000000000000000000000000000012321")],
193 bytes!(
194 "
195 0000000000000000000000000000000000000000000000000000000000012345
196 0000000000000000000000000000000000000000000000000000000000054321
197 "
198 ),
199 );
200 let event = DynSolEvent {
201 topic_0: Some(t0),
202 indexed: vec![DynSolType::Address],
203 body: DynSolType::Tuple(vec![DynSolType::Tuple(vec![
204 DynSolType::Address,
205 DynSolType::Address,
206 ])]),
207 };
208
209 let decoded = event.decode_log_data(&log).unwrap();
210 assert_eq!(
211 decoded.indexed,
212 vec![DynSolValue::Address(address!("0x0000000000000000000000000000000000012321"))]
213 );
214
215 let encoded = decoded.encode_log_data();
216 assert_eq!(encoded, log);
217 }
218}