alloy_dyn_abi/ext/
event.rs

1use crate::{DecodedEvent, DynSolEvent, DynSolType, Error, Result, Specifier};
2use alloc::vec::Vec;
3use alloy_json_abi::Event;
4use alloy_primitives::{LogData, B256};
5
6#[allow(unknown_lints, unnameable_types)]
7mod sealed {
8    pub trait Sealed {}
9    impl Sealed for alloy_json_abi::Event {}
10}
11use sealed::Sealed;
12
13impl Specifier<DynSolEvent> for Event {
14    fn resolve(&self) -> Result<DynSolEvent> {
15        let mut indexed = Vec::with_capacity(self.inputs.len());
16        let mut body = Vec::with_capacity(self.inputs.len());
17        for param in &self.inputs {
18            let ty = param.resolve()?;
19            if param.indexed {
20                indexed.push(ty);
21            } else {
22                body.push(ty);
23            }
24        }
25        let topic_0 = if self.anonymous { None } else { Some(self.selector()) };
26
27        let num_topics = indexed.len() + topic_0.is_some() as usize;
28        if num_topics > 4 {
29            return Err(Error::TopicLengthMismatch { expected: 4, actual: num_topics });
30        }
31
32        Ok(DynSolEvent::new_unchecked(topic_0, indexed, DynSolType::Tuple(body)))
33    }
34}
35
36/// Provides event encoding and decoding for the [`Event`] type.
37///
38/// This trait is sealed and cannot be implemented for types outside of this
39/// crate. It is implemented only for [`Event`].
40pub trait EventExt: Sealed {
41    /// Decodes the given log info according to this item's input types.
42    ///
43    /// The `topics` parameter is the list of indexed topics, and the `data`
44    /// parameter is the non-indexed data.
45    ///
46    /// The first topic is skipped, unless the event is anonymous.
47    ///
48    /// For more details, see the [Solidity reference][ref].
49    ///
50    /// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#encoding-of-indexed-event-parameters
51    ///
52    /// # Errors
53    ///
54    /// This function will return an error if the decoded data does not match
55    /// the expected input types.
56    fn decode_log_parts<I>(&self, topics: I, data: &[u8]) -> Result<DecodedEvent>
57    where
58        I: IntoIterator<Item = B256>;
59
60    /// Decodes the given log object according to this item's input types.
61    ///
62    /// See [`decode_log`](EventExt::decode_log).
63    #[inline]
64    fn decode_log(&self, log: &LogData) -> Result<DecodedEvent> {
65        self.decode_log_parts(log.topics().iter().copied(), &log.data)
66    }
67}
68
69impl EventExt for Event {
70    fn decode_log_parts<I>(&self, topics: I, data: &[u8]) -> Result<DecodedEvent>
71    where
72        I: IntoIterator<Item = B256>,
73    {
74        self.resolve()?.decode_log_parts(topics, data)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::DynSolValue;
82    use alloy_json_abi::EventParam;
83    use alloy_primitives::{address, b256, bytes, hex, keccak256, Signed};
84
85    #[test]
86    fn empty() {
87        let mut event = Event { name: "MyEvent".into(), inputs: vec![], anonymous: false };
88
89        let values = event.decode_log_parts(Some(keccak256("MyEvent()")), &[]).unwrap();
90        assert!(values.indexed.is_empty());
91        assert!(values.body.is_empty());
92        event.anonymous = true;
93        let values = event.decode_log_parts(None, &[]).unwrap();
94        assert!(values.indexed.is_empty());
95        assert!(values.body.is_empty());
96        let values = event.decode_log_parts(None, &[]).unwrap();
97        assert!(values.indexed.is_empty());
98        assert!(values.body.is_empty());
99    }
100
101    // https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/event.rs#L192
102    #[test]
103    fn test_decoding_event() {
104        let event = Event {
105            name: "foo".into(),
106            inputs: vec![
107                EventParam { ty: "int256".into(), indexed: false, ..Default::default() },
108                EventParam { ty: "int256".into(), indexed: true, ..Default::default() },
109                EventParam { ty: "address".into(), indexed: false, ..Default::default() },
110                EventParam { ty: "address".into(), indexed: true, ..Default::default() },
111                EventParam { ty: "string".into(), indexed: true, ..Default::default() },
112            ],
113            anonymous: false,
114        };
115
116        let result = event
117            .decode_log_parts(
118                [
119                    event.selector(),
120                    b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
121                    b256!("0x0000000000000000000000001111111111111111111111111111111111111111"),
122                    b256!("0x00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
123                ],
124                &hex!(
125                    "
126                    0000000000000000000000000000000000000000000000000000000000000003
127                    0000000000000000000000002222222222222222222222222222222222222222
128                "
129                ),
130            )
131            .unwrap();
132
133        assert_eq!(
134            result.body,
135            [
136                DynSolValue::Int(
137                    Signed::from_be_bytes(hex!(
138                        "0000000000000000000000000000000000000000000000000000000000000003"
139                    )),
140                    256
141                ),
142                DynSolValue::Address(address!("0x2222222222222222222222222222222222222222")),
143            ]
144        );
145        assert_eq!(
146            result.indexed,
147            [
148                DynSolValue::Int(
149                    Signed::from_be_bytes(hex!(
150                        "0000000000000000000000000000000000000000000000000000000000000002"
151                    )),
152                    256
153                ),
154                DynSolValue::Address(address!("0x1111111111111111111111111111111111111111")),
155                DynSolValue::FixedBytes(
156                    b256!("0x00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
157                    32
158                ),
159            ]
160        )
161    }
162
163    #[test]
164    fn parse_log_whole() {
165        let correct_event = Event {
166            name: "Test".into(),
167            inputs: vec![
168                EventParam { ty: "(address,address)".into(), indexed: false, ..Default::default() },
169                EventParam { ty: "address".into(), indexed: true, ..Default::default() },
170            ],
171            anonymous: false,
172        };
173        // swap indexed params
174        let mut wrong_event = correct_event.clone();
175        wrong_event.inputs[0].indexed = true;
176        wrong_event.inputs[1].indexed = false;
177
178        let log = LogData::new_unchecked(
179            vec![
180                b256!("0xcf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7"),
181                b256!("0x0000000000000000000000000000000000000000000000000000000000012321"),
182            ],
183            bytes!(
184                "
185			0000000000000000000000000000000000000000000000000000000000012345
186			0000000000000000000000000000000000000000000000000000000000054321
187			"
188            ),
189        );
190
191        wrong_event.decode_log(&log).unwrap();
192        correct_event.decode_log(&log).unwrap();
193        correct_event.decode_log(&log).unwrap();
194    }
195}