solabi/
event.rs

1//! Module containing Solidity event related traits and logic.
2
3use crate::{
4    decode::{Decode, DecodeError},
5    encode::Encode,
6    fmt::Hex,
7    log::{FromTopic, Log, ToTopic, Topics},
8    primitive::Word,
9};
10use std::{
11    error::Error,
12    fmt::{self, Debug, Display, Formatter},
13    marker::PhantomData,
14};
15
16/// A trait for converting to and from event indices.
17pub trait Indexed: Sized {
18    fn from_topics(topics: &Topics) -> Result<(Word, Self), FromTopicsError>;
19    fn to_topics(topic0: &Word, indices: &Self) -> Topics;
20}
21
22/// A trait for converting to and from event indices.
23pub trait IndexedAnonymous: Sized {
24    fn from_topics_anonymous(topics: &Topics) -> Result<Self, FromTopicsError>;
25    fn to_topics_anonymous(&self) -> Topics;
26}
27
28/// An event encoder with a known topic0.
29pub struct EventEncoder<I, D> {
30    /// The event topic 0.
31    pub topic0: Word,
32    _marker: PhantomData<*const (I, D)>,
33}
34
35impl<I, D> EventEncoder<I, D>
36where
37    I: Indexed,
38{
39    /// Creates a new typed event from a topic0.
40    pub const fn new(topic0: Word) -> Self {
41        Self {
42            topic0,
43            _marker: PhantomData,
44        }
45    }
46}
47
48impl<I, D> EventEncoder<I, D>
49where
50    I: Indexed,
51    D: Encode,
52{
53    /// Encode event data into an EVM log.
54    pub fn encode(&self, indices: &I, data: &D) -> Log<'_> {
55        Log {
56            topics: I::to_topics(&self.topic0, indices),
57            data: crate::encode(data).into(),
58        }
59    }
60}
61
62impl<I, D> EventEncoder<I, D>
63where
64    I: Indexed,
65    D: Decode,
66{
67    /// Decode event data from an EVM log.
68    pub fn decode(&self, log: &Log) -> Result<(I, D), ParseError> {
69        let (topic0, indices) = I::from_topics(&log.topics)?;
70        if topic0 != self.topic0 {
71            return Err(ParseError::SelectorMismatch(topic0));
72        }
73        let data = crate::decode(&log.data)?;
74
75        Ok((indices, data))
76    }
77}
78
79impl<I, D> Debug for EventEncoder<I, D> {
80    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
81        f.debug_struct("EventEncoder")
82            .field("topic0", &Hex(&self.topic0))
83            .finish()
84    }
85}
86
87/// An anonymous event encoder.
88pub struct AnonymousEventEncoder<I, D>(PhantomData<*const (I, D)>);
89
90impl<I, D> AnonymousEventEncoder<I, D>
91where
92    I: IndexedAnonymous,
93{
94    /// Creates a new anonymous event.
95    pub fn new() -> Self {
96        Self(PhantomData)
97    }
98}
99
100impl<I, D> AnonymousEventEncoder<I, D>
101where
102    I: IndexedAnonymous,
103    D: Encode,
104{
105    /// Encode event data into an EVM log.
106    pub fn encode(&self, indices: &I, data: &D) -> Log<'_> {
107        Log {
108            topics: I::to_topics_anonymous(indices),
109            data: crate::encode(data).into(),
110        }
111    }
112}
113
114impl<I, D> AnonymousEventEncoder<I, D>
115where
116    I: IndexedAnonymous,
117    D: Decode,
118{
119    /// Decode event data from an EVM log.
120    pub fn decode(&self, log: &Log) -> Result<(I, D), ParseError> {
121        let indices = I::from_topics_anonymous(&log.topics)?;
122        let data = crate::decode(&log.data)?;
123
124        Ok((indices, data))
125    }
126}
127
128impl<I, D> Debug for AnonymousEventEncoder<I, D>
129where
130    I: IndexedAnonymous,
131{
132    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
133        f.debug_tuple("AnonymousEventEncoder").finish()
134    }
135}
136
137impl<I, D> Default for AnonymousEventEncoder<I, D>
138where
139    I: IndexedAnonymous,
140{
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146/// An error parsing an event.
147pub enum ParseError {
148    /// An error parsing event indices.
149    Topics(FromTopicsError),
150    /// The event's topic0 does not match the log's topic0.
151    SelectorMismatch(Word),
152    /// An error decoding log data.
153    Data(DecodeError),
154}
155
156impl Debug for ParseError {
157    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
158        match self {
159            Self::Topics(err) => f.debug_tuple("FromTopics").field(err).finish(),
160            Self::SelectorMismatch(topic0) => f
161                .debug_tuple("SelectorMismatch")
162                .field(&Hex(topic0))
163                .finish(),
164            Self::Data(err) => f.debug_tuple("Data").field(err).finish(),
165        }
166    }
167}
168
169impl Display for ParseError {
170    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
171        match self {
172            Self::Topics(err) => write!(f, "{err}"),
173            Self::SelectorMismatch(_) => f.write_str("event topic0 does not match log's topic0"),
174            Self::Data(err) => write!(f, "{err}"),
175        }
176    }
177}
178
179impl Error for ParseError {}
180
181impl From<FromTopicsError> for ParseError {
182    fn from(err: FromTopicsError) -> Self {
183        Self::Topics(err)
184    }
185}
186
187impl From<DecodeError> for ParseError {
188    fn from(err: DecodeError) -> Self {
189        Self::Data(err)
190    }
191}
192
193/// An error parsing log topics.
194#[derive(Debug)]
195pub enum FromTopicsError {
196    /// The log contains the wrong number of topics.
197    WrongCount,
198    /// The topic data was invalid.
199    InvalidData,
200}
201
202impl Display for FromTopicsError {
203    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
204        match self {
205            Self::WrongCount => f.write_str("event indices does not match log topics"),
206            Self::InvalidData => f.write_str("log topic data is invalid for event index"),
207        }
208    }
209}
210
211impl Error for FromTopicsError {}
212
213macro_rules! impl_indexed {
214    ($($t:ident),*) => {
215        #[allow(non_snake_case, unused_variables)]
216        impl<$($t),*> Indexed for ($($t,)*)
217        where
218            $($t: ToTopic + FromTopic,)*
219        {
220            fn from_topics(topics: &Topics) -> Result<(Word, Self), FromTopicsError> {
221                let mut topics = topics.iter().copied();
222                let topic0 = topics.next().ok_or(FromTopicsError::WrongCount)?;
223                $(let $t = $t::from_topic(topics.next().ok_or(FromTopicsError::WrongCount)?)
224                    .ok_or(FromTopicsError::InvalidData)?;)*
225                if topics.next().is_some() {
226                    return Err(FromTopicsError::WrongCount);
227                }
228                Ok((topic0, ($($t,)*)))
229            }
230
231            fn to_topics(topic0: &Word, ($($t,)*): &Self) -> Topics {
232                Topics::from([*topic0, $($t.to_topic()),*])
233            }
234        }
235
236        impl_indexed! { anonymous: $($t),* }
237    };
238
239    (anonymous: $($t:ident),*) => {
240        #[allow(non_snake_case, unused_mut, unused_variables)]
241        impl<$($t),*> IndexedAnonymous for ($($t,)*)
242        where
243            $($t: ToTopic + FromTopic,)*
244        {
245            fn from_topics_anonymous(topics: &Topics) -> Result<Self, FromTopicsError> {
246                let mut topics = topics.iter().copied();
247                $(let $t = $t::from_topic(topics.next().ok_or(FromTopicsError::WrongCount)?)
248                    .ok_or(FromTopicsError::InvalidData)?;)*
249                if topics.next().is_some() {
250                    return Err(FromTopicsError::WrongCount);
251                }
252                Ok(($($t,)*))
253            }
254
255            fn to_topics_anonymous(&self) -> Topics {
256                let ($($t,)*) = self;
257                Topics::from([$($t.to_topic()),*])
258            }
259        }
260    };
261}
262
263impl_indexed! {}
264impl_indexed! { A }
265impl_indexed! { A, B }
266impl_indexed! { A, B, C }
267impl_indexed! { anonymous: A, B, C, D }
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use crate::bytes::Bytes;
273    use ethprim::{address, uint, Address, U256};
274    use hex_literal::hex;
275    use std::borrow::Cow;
276
277    #[test]
278    fn transfer_event_roundtrip() {
279        let transfer = EventEncoder::<(Address, Address), (U256,)>::new(hex!(
280            "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
281        ));
282
283        let from = address!("0x0101010101010101010101010101010101010101");
284        let to = address!("0x0202020202020202020202020202020202020202");
285        let value = uint!("4_200_000_000_000_000_000");
286
287        let log = Log {
288            topics: Topics::from([
289                transfer.topic0,
290                hex!("0000000000000000000000000101010101010101010101010101010101010101"),
291                hex!("0000000000000000000000000202020202020202020202020202020202020202"),
292            ]),
293            data: hex!("0000000000000000000000000000000000000000000000003a4965bf58a40000")[..]
294                .into(),
295        };
296
297        assert_eq!(transfer.decode(&log).unwrap(), ((from, to), (value,)),);
298        assert_eq!(transfer.encode(&(from, to), &(value,)), log);
299    }
300
301    #[test]
302    fn fails_to_decode_event_with_different_indices() {
303        let selector = hex!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
304
305        let transfer = EventEncoder::<(Address, Address), (U256,)>::new(selector);
306        let log = transfer.encode(
307            &(
308                address!("0x0101010101010101010101010101010101010101"),
309                address!("0x0202020202020202020202020202020202020202"),
310            ),
311            &(uint!("4_200_000_000_000_000_000"),),
312        );
313
314        let transfer = EventEncoder::<(Address, Address, U256), ()>::new(selector);
315        assert!(matches!(
316            transfer.decode(&log),
317            Err(ParseError::Topics(FromTopicsError::WrongCount))
318        ));
319
320        let transfer = EventEncoder::<(Address,), (Address, U256)>::new(selector);
321        assert!(matches!(
322            transfer.decode(&log),
323            Err(ParseError::Topics(FromTopicsError::WrongCount))
324        ));
325    }
326
327    #[test]
328    fn empty_indices_and_data() {
329        let event = EventEncoder::<(), (U256,)>::new([1; 32]);
330        let value = uint!("42");
331        let log = Log {
332            topics: Topics::from([event.topic0]),
333            data: hex!("000000000000000000000000000000000000000000000000000000000000002a")[..]
334                .into(),
335        };
336        assert_eq!(event.decode(&log).unwrap(), ((), (value,)),);
337        assert_eq!(event.encode(&(), &(value,)), log);
338
339        let event = EventEncoder::<(U256,), ()>::new([2; 32]);
340        let value = uint!("42");
341        let log = Log {
342            topics: Topics::from([
343                event.topic0,
344                hex!("000000000000000000000000000000000000000000000000000000000000002a"),
345            ]),
346            data: hex!("")[..].into(),
347        };
348        assert_eq!(event.decode(&log).unwrap(), ((value,), ()),);
349        assert_eq!(event.encode(&(value,), &()), log);
350
351        let event = EventEncoder::<(), ()>::new([2; 32]);
352        let log = Log {
353            topics: Topics::from([event.topic0]),
354            data: hex!("")[..].into(),
355        };
356        assert_eq!(event.decode(&log).unwrap(), ((), ()),);
357        assert_eq!(event.encode(&(), &()), log);
358    }
359
360    #[test]
361    fn anonymous_event_with_indexed_dynamic_field() {
362        let anon = AnonymousEventEncoder::<
363            (Cow<str>, Cow<[(U256, (bool, Cow<Bytes<[u8]>>))]>),
364            (U256, U256),
365        >::new();
366
367        let indices = (
368            "hello world".into(),
369            vec![
370                (U256::MAX - 1, (true, Bytes::borrowed(&[1, 2, 3]))),
371                (U256::MAX - 2, (true, Bytes::borrowed(&[4, 5, 6]))),
372            ]
373            .into(),
374        );
375        let fields = (uint!("1"), uint!("2"));
376
377        let log = Log {
378            topics: Topics::from([
379                hex!("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"),
380                hex!("6b8a0e75eceddd0e7d4d0413a720bce2cb899061e362357db170c49c5563672f"),
381            ]),
382            data: hex!(
383                "0000000000000000000000000000000000000000000000000000000000000001
384                 0000000000000000000000000000000000000000000000000000000000000002"
385            )[..]
386                .into(),
387        };
388
389        assert_eq!(anon.encode(&indices, &fields), log);
390
391        // Note that indexed dynamic fields are **not** actually recoverable.
392        assert_eq!(anon.decode(&log).unwrap(), (Default::default(), fields));
393    }
394}