alloy_dyn_abi/dynamic/
event.rs

1use crate::{DynSolType, DynSolValue, Error, Result};
2use alloc::vec::Vec;
3use alloy_primitives::{IntoLogData, Log, LogData, B256};
4
5/// A dynamic ABI event.
6///
7/// This is a representation of a Solidity event, which can be used to decode
8/// logs.
9#[derive(Clone, Debug, PartialEq)]
10pub struct DynSolEvent {
11    /// The event signature hash, if any.
12    pub(crate) topic_0: Option<B256>,
13    /// The indexed types.
14    pub(crate) indexed: Vec<DynSolType>,
15    /// The un-indexed types.
16    pub(crate) body: DynSolType,
17}
18
19impl DynSolEvent {
20    /// Creates a new event, without length-checking the indexed, or ensuring
21    /// the body is a tuple. This allows creation of invalid events.
22    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    /// Creates a new event.
31    ///
32    /// Checks that the indexed length is less than or equal to 4, and that the
33    /// body is a tuple.
34    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    /// True if anonymous.
43    pub const fn is_anonymous(&self) -> bool {
44        self.topic_0.is_none()
45    }
46
47    /// Decode the event from the given log info.
48    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        // skip event hash if not anonymous
63        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    /// Decode the event from the given log info.
100    pub fn decode_log_data(&self, log: &LogData) -> Result<DecodedEvent> {
101        self.decode_log_parts(log.topics().iter().copied(), &log.data)
102    }
103
104    /// Get the selector for this event, if any.
105    pub const fn topic_0(&self) -> Option<B256> {
106        self.topic_0
107    }
108
109    /// Get the indexed types.
110    pub fn indexed(&self) -> &[DynSolType] {
111        &self.indexed
112    }
113
114    /// Get the un-indexed types.
115    pub fn body(&self) -> &[DynSolType] {
116        self.body.as_tuple().expect("body is a tuple")
117    }
118}
119
120/// A decoded dynamic ABI event.
121#[derive(Clone, Debug, PartialEq)]
122pub struct DecodedEvent {
123    /// The hashes event_signature (if any)
124    #[doc(alias = "topic_0")]
125    pub selector: Option<B256>,
126    /// The indexed values, in order.
127    pub indexed: Vec<DynSolValue>,
128    /// The un-indexed values, in order.
129    pub body: Vec<DynSolValue>,
130}
131
132impl DecodedEvent {
133    /// True if anonymous. False if not.
134    pub const fn is_anonymous(&self) -> bool {
135        self.selector.is_none()
136    }
137
138    /// Re-encode the event into a [`LogData`]
139    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    /// Transform a [`Log`] containing this event into a [`Log`] containing
156    /// [`LogData`].
157    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}