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], validate: bool) -> 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, validate: bool) -> Result<DecodedEvent> {
65        self.decode_log_parts(log.topics().iter().copied(), &log.data, validate)
66    }
67}
68
69impl EventExt for Event {
70    fn decode_log_parts<I>(&self, topics: I, data: &[u8], validate: bool) -> Result<DecodedEvent>
71    where
72        I: IntoIterator<Item = B256>,
73    {
74        self.resolve()?.decode_log_parts(topics, data, validate)
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        // skips over hash
90        let values = event.decode_log_parts(None, &[], false).unwrap();
91        assert!(values.indexed.is_empty());
92        assert!(values.body.is_empty());
93
94        // but if we validate, we get an error
95        let err = event.decode_log_parts(None, &[], true).unwrap_err();
96        assert_eq!(err, Error::TopicLengthMismatch { expected: 1, actual: 0 });
97
98        let values = event.decode_log_parts(Some(keccak256("MyEvent()")), &[], true).unwrap();
99        assert!(values.indexed.is_empty());
100        assert!(values.body.is_empty());
101        event.anonymous = true;
102        let values = event.decode_log_parts(None, &[], false).unwrap();
103        assert!(values.indexed.is_empty());
104        assert!(values.body.is_empty());
105        let values = event.decode_log_parts(None, &[], true).unwrap();
106        assert!(values.indexed.is_empty());
107        assert!(values.body.is_empty());
108    }
109
110    // https://github.com/rust-ethereum/ethabi/blob/b1710adc18f5b771d2d2519c87248b1ba9430778/ethabi/src/event.rs#L192
111    #[test]
112    fn test_decoding_event() {
113        let event = Event {
114            name: "foo".into(),
115            inputs: vec![
116                EventParam { ty: "int256".into(), indexed: false, ..Default::default() },
117                EventParam { ty: "int256".into(), indexed: true, ..Default::default() },
118                EventParam { ty: "address".into(), indexed: false, ..Default::default() },
119                EventParam { ty: "address".into(), indexed: true, ..Default::default() },
120                EventParam { ty: "string".into(), indexed: true, ..Default::default() },
121            ],
122            anonymous: false,
123        };
124
125        let result = event
126            .decode_log_parts(
127                [
128                    b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
129                    b256!("0x0000000000000000000000000000000000000000000000000000000000000002"),
130                    b256!("0x0000000000000000000000001111111111111111111111111111111111111111"),
131                    b256!("0x00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
132                    b256!("0x00000000000000000bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
133                    b256!("0x00000000000000000ccccccccccccccccccccccccccccccccccccccccccccccc"),
134                ],
135                &hex!(
136                    "
137                    0000000000000000000000000000000000000000000000000000000000000003
138                    0000000000000000000000002222222222222222222222222222222222222222
139                "
140                ),
141                false,
142            )
143            .unwrap();
144
145        assert_eq!(
146            result.body,
147            [
148                DynSolValue::Int(
149                    Signed::from_be_bytes(hex!(
150                        "0000000000000000000000000000000000000000000000000000000000000003"
151                    )),
152                    256
153                ),
154                DynSolValue::Address(address!("0x2222222222222222222222222222222222222222")),
155            ]
156        );
157        assert_eq!(
158            result.indexed,
159            [
160                DynSolValue::Int(
161                    Signed::from_be_bytes(hex!(
162                        "0000000000000000000000000000000000000000000000000000000000000002"
163                    )),
164                    256
165                ),
166                DynSolValue::Address(address!("0x1111111111111111111111111111111111111111")),
167                DynSolValue::FixedBytes(
168                    b256!("0x00000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
169                    32
170                ),
171            ]
172        )
173    }
174
175    #[test]
176    fn parse_log_whole() {
177        let correct_event = Event {
178            name: "Test".into(),
179            inputs: vec![
180                EventParam { ty: "(address,address)".into(), indexed: false, ..Default::default() },
181                EventParam { ty: "address".into(), indexed: true, ..Default::default() },
182            ],
183            anonymous: false,
184        };
185        // swap indexed params
186        let mut wrong_event = correct_event.clone();
187        wrong_event.inputs[0].indexed = true;
188        wrong_event.inputs[1].indexed = false;
189
190        let log = LogData::new_unchecked(
191            vec![
192                b256!("0xcf74b4e62f836eeedcd6f92120ffb5afea90e6fa490d36f8b81075e2a7de0cf7"),
193                b256!("0x0000000000000000000000000000000000000000000000000000000000012321"),
194            ],
195            bytes!(
196                "
197			0000000000000000000000000000000000000000000000000000000000012345
198			0000000000000000000000000000000000000000000000000000000000054321
199			"
200            ),
201        );
202
203        wrong_event.decode_log(&log, false).unwrap();
204        // TODO: How do we verify here?
205        // wrong_event.decode_log_object(&log, true).unwrap_err();
206        correct_event.decode_log(&log, false).unwrap();
207        correct_event.decode_log(&log, true).unwrap();
208    }
209}