alloy_sol_types/types/event/
mod.rs

1use crate::{
2    abi::token::{Token, TokenSeq, WordToken},
3    Result, SolType, Word,
4};
5use alloc::vec::Vec;
6use alloy_primitives::{FixedBytes, Log, LogData, B256};
7
8mod topic;
9pub use topic::EventTopic;
10
11mod topic_list;
12pub use topic_list::TopicList;
13
14/// Solidity event.
15///
16/// # Implementer's Guide
17///
18/// It should not be necessary to implement this trait manually. Instead, use
19/// the [`sol!`](crate::sol!) procedural macro to parse Solidity syntax into
20/// types that implement this trait.
21pub trait SolEvent: Sized {
22    /// The underlying tuple type which represents this event's non-indexed
23    /// parameters. These parameters are ABI encoded and included in the log
24    /// body.
25    ///
26    /// If this event has no non-indexed parameters, this will be the unit type
27    /// `()`.
28    type DataTuple<'a>: SolType<Token<'a> = Self::DataToken<'a>>;
29
30    /// The [`TokenSeq`] type corresponding to the tuple.
31    type DataToken<'a>: TokenSeq<'a>;
32
33    /// The underlying tuple type which represents this event's topics.
34    ///
35    /// These are ABI encoded and included in the log struct returned by the
36    /// RPC node.
37    ///
38    /// See the [`TopicList`] trait for more details.
39    type TopicList: TopicList;
40
41    /// The event's ABI signature.
42    ///
43    /// For anonymous events, this is unused, but is still present.
44    const SIGNATURE: &'static str;
45
46    /// The event's ABI signature hash, or selector: `keccak256(SIGNATURE)`
47    ///
48    /// For non-anonymous events, this will be the first topic (`topic0`).
49    /// For anonymous events, this is unused, but is still present.
50    #[doc(alias = "SELECTOR")]
51    const SIGNATURE_HASH: FixedBytes<32>;
52
53    /// Whether the event is anonymous.
54    const ANONYMOUS: bool;
55
56    /// Convert decoded rust data to the event type.
57    ///
58    /// Does not check that `topics[0]` is the correct hash.
59    /// Use [`new_checked`](Self::new_checked) instead.
60    fn new(
61        topics: <Self::TopicList as SolType>::RustType,
62        data: <Self::DataTuple<'_> as SolType>::RustType,
63    ) -> Self;
64
65    /// Convert decoded rust data to the event type.
66    ///
67    /// Checks that `topics[0]` is the correct hash.
68    #[inline]
69    fn new_checked(
70        topics: <Self::TopicList as SolType>::RustType,
71        data: <Self::DataTuple<'_> as SolType>::RustType,
72    ) -> Result<Self> {
73        Self::check_signature(&topics).map(|()| Self::new(topics, data))
74    }
75
76    /// Check that the event's signature matches the given topics.
77    #[inline]
78    fn check_signature(topics: &<Self::TopicList as SolType>::RustType) -> Result<()> {
79        // Overridden for non-anonymous events in `sol!`.
80        let _ = topics;
81        Ok(())
82    }
83
84    /// Tokenize the event's non-indexed parameters.
85    fn tokenize_body(&self) -> Self::DataToken<'_>;
86
87    // TODO: avoid clones here
88    /// The event's topics.
89    fn topics(&self) -> <Self::TopicList as SolType>::RustType;
90
91    /// The size of the ABI-encoded dynamic data in bytes.
92    #[inline]
93    fn abi_encoded_size(&self) -> usize {
94        if let Some(size) = <Self::DataTuple<'_> as SolType>::ENCODED_SIZE {
95            return size;
96        }
97
98        self.tokenize_body().total_words() * Word::len_bytes()
99    }
100
101    /// ABI-encode the dynamic data of this event into the given buffer.
102    #[inline]
103    fn encode_data_to(&self, out: &mut Vec<u8>) {
104        out.reserve(self.abi_encoded_size());
105        out.extend(crate::abi::encode_sequence(&self.tokenize_body()));
106    }
107
108    /// ABI-encode the dynamic data of this event.
109    #[inline]
110    fn encode_data(&self) -> Vec<u8> {
111        let mut out = Vec::new();
112        self.encode_data_to(&mut out);
113        out
114    }
115
116    /// Encode the topics of this event into the given buffer.
117    ///
118    /// # Errors
119    ///
120    /// This method should return an error only if the buffer is too small.
121    fn encode_topics_raw(&self, out: &mut [WordToken]) -> Result<()>;
122
123    /// Encode the topics of this event.
124    ///
125    /// The returned vector will have length `Self::TopicList::COUNT`.
126    #[inline]
127    fn encode_topics(&self) -> Vec<WordToken> {
128        let mut out = vec![WordToken(B256::ZERO); Self::TopicList::COUNT];
129        self.encode_topics_raw(&mut out).unwrap();
130        out
131    }
132
133    /// Encode the topics of this event into a fixed-size array.
134    ///
135    /// This method will not compile if `LEN` is not equal to `Self::TopicList::COUNT`.
136    #[inline]
137    fn encode_topics_array<const LEN: usize>(&self) -> [WordToken; LEN] {
138        const { assert!(LEN == Self::TopicList::COUNT, "topic list length mismatch") };
139        let mut out = [WordToken(B256::ZERO); LEN];
140        self.encode_topics_raw(&mut out).unwrap();
141        out
142    }
143
144    /// Encode this event to a [`LogData`].
145    fn encode_log_data(&self) -> LogData {
146        LogData::new_unchecked(
147            self.encode_topics().into_iter().map(Into::into).collect(),
148            self.encode_data().into(),
149        )
150    }
151
152    /// Transform ca [`Log`] containing this event into a [`Log`] containing
153    /// [`LogData`].
154    fn encode_log(log: &Log<Self>) -> Log<LogData> {
155        Log { address: log.address, data: log.data.encode_log_data() }
156    }
157
158    /// Decode the topics of this event from the given data.
159    #[inline]
160    fn decode_topics<I, D>(topics: I) -> Result<<Self::TopicList as SolType>::RustType>
161    where
162        I: IntoIterator<Item = D>,
163        D: Into<WordToken>,
164    {
165        <Self::TopicList as TopicList>::detokenize(topics)
166    }
167
168    /// ABI-decodes the dynamic data of this event from the given buffer.
169    #[inline]
170    fn abi_decode_data<'a>(data: &'a [u8]) -> Result<<Self::DataTuple<'a> as SolType>::RustType> {
171        <Self::DataTuple<'a> as SolType>::abi_decode_sequence(data)
172    }
173
174    /// ABI-decodes the dynamic data of this event from the given buffer, with validation.
175    ///
176    /// This is the same as [`abi_decode_data`](Self::abi_decode_data), but performs
177    /// validation checks on the decoded data tuple.
178    #[inline]
179    fn abi_decode_data_validate<'a>(
180        data: &'a [u8],
181    ) -> Result<<Self::DataTuple<'a> as SolType>::RustType> {
182        <Self::DataTuple<'a> as SolType>::abi_decode_sequence_validate(data)
183    }
184
185    /// Decode the event from the given log info.
186    fn decode_raw_log<I, D>(topics: I, data: &[u8]) -> Result<Self>
187    where
188        I: IntoIterator<Item = D>,
189        D: Into<WordToken>,
190    {
191        let topics = Self::decode_topics(topics)?;
192        // Check signature before decoding the data.
193        Self::check_signature(&topics)?;
194        let body = Self::abi_decode_data(data)?;
195        Ok(Self::new(topics, body))
196    }
197
198    /// Decode the event from the given log info, with validation.
199    ///
200    /// This is the same as [`decode_raw_log`](Self::decode_raw_log), but performs
201    /// validation checks on the decoded topics and data.
202    fn decode_raw_log_validate<I, D>(topics: I, data: &[u8]) -> Result<Self>
203    where
204        I: IntoIterator<Item = D>,
205        D: Into<WordToken>,
206    {
207        let topics = Self::decode_topics(topics)?;
208        // Check signature before decoding the data.
209        Self::check_signature(&topics)?;
210        let body = Self::abi_decode_data_validate(data)?;
211        Ok(Self::new(topics, body))
212    }
213
214    /// Decode the event from the given log object.
215    fn decode_log_data(log: &LogData) -> Result<Self> {
216        Self::decode_raw_log(log.topics(), &log.data)
217    }
218
219    /// Decode the event from the given log object.
220    fn decode_log(log: &Log) -> Result<Log<Self>> {
221        Self::decode_log_data(&log.data).map(|data| Log { address: log.address, data })
222    }
223}