ink_ir/ir/event/
mod.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod config;
16mod signature_topic;
17
18pub use config::EventConfig;
19pub use signature_topic::SignatureTopic;
20
21use proc_macro2::{
22    Span,
23    TokenStream as TokenStream2,
24};
25use quote::ToTokens;
26use syn::spanned::Spanned as _;
27
28use crate::{
29    error::ExtError,
30    ir,
31    utils::extract_cfg_attributes,
32};
33
34/// A checked ink! event with its configuration.
35#[derive(Debug, PartialEq, Eq)]
36pub struct Event {
37    item: syn::ItemStruct,
38    config: EventConfig,
39}
40
41impl Event {
42    /// Returns `Ok` if the input matches all requirements for an ink! event.
43    pub fn new(config: TokenStream2, item: TokenStream2) -> Result<Self, syn::Error> {
44        let item = syn::parse2::<syn::ItemStruct>(item.clone()).map_err(|err| {
45            err.into_combine(format_err_spanned!(
46                item,
47                "event definition must be a `struct`",
48            ))
49        })?;
50        let parsed_config = syn::parse2::<crate::ast::AttributeArgs>(config)?;
51        let config = EventConfig::try_from(parsed_config)?;
52
53        for attr in &item.attrs {
54            if attr.path().to_token_stream().to_string().contains("event") {
55                return Err(format_err_spanned!(
56                    attr,
57                    "only one `ink::event` is allowed",
58                ));
59            }
60        }
61
62        Ok(Self { item, config })
63    }
64
65    /// Returns the event definition .
66    pub fn item(&self) -> &syn::ItemStruct {
67        &self.item
68    }
69
70    /// Returns `true` if the first ink! annotation on the given struct is
71    /// `#[ink(event)]`.
72    ///
73    /// # Errors
74    ///
75    /// If the first found ink! attribute is malformed.
76    ///
77    /// # Note
78    ///
79    /// This is used for legacy "inline" event definitions, i.e. event definitions that
80    /// are defined within a module annotated with `#[ink::contract]`.
81    pub(super) fn is_ink_event(
82        item_struct: &syn::ItemStruct,
83    ) -> Result<bool, syn::Error> {
84        if !ir::contains_ink_attributes(&item_struct.attrs) {
85            return Ok(false);
86        }
87        // At this point we know that there must be at least one ink!
88        // attribute. This can be either the ink! storage struct,
89        // an ink! event or an invalid ink! attribute.
90        let attr = ir::first_ink_attribute(&item_struct.attrs)?
91            .expect("missing expected ink! attribute for struct");
92        Ok(matches!(attr.first().kind(), ir::AttributeArg::Event))
93    }
94
95    /// Returns if the event is marked as anonymous, if true then no signature topic is
96    /// generated or emitted.
97    pub fn anonymous(&self) -> bool {
98        self.config.anonymous()
99    }
100
101    /// Return manually specified signature topic hash.
102    ///
103    /// # Note
104    ///
105    /// Conflicts with `anonymous`
106    pub fn signature_topic(&self) -> Option<SignatureTopic> {
107        self.config.signature_topic()
108    }
109
110    /// Returns a list of `cfg` attributes if any.
111    pub fn get_cfg_attrs(&self, span: Span) -> Vec<TokenStream2> {
112        extract_cfg_attributes(&self.item.attrs, span)
113    }
114
115    /// Returns the event name override (if any).
116    pub fn name(&self) -> Option<&str> {
117        self.config.name()
118    }
119}
120
121impl ToTokens for Event {
122    /// We mainly implement this trait for this ink! type to have a derived
123    /// [`Spanned`](`syn::spanned::Spanned`) implementation for it.
124    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
125        self.item.to_tokens(tokens)
126    }
127}
128
129impl TryFrom<syn::ItemStruct> for Event {
130    type Error = syn::Error;
131
132    fn try_from(item_struct: syn::ItemStruct) -> Result<Self, Self::Error> {
133        let struct_span = item_struct.span();
134        let (ink_attrs, other_attrs) = ir::sanitize_attributes(
135            struct_span,
136            item_struct.attrs.clone(),
137            &ir::AttributeArgKind::Event,
138            |arg| {
139                match arg.kind() {
140                    ir::AttributeArg::Event
141                    | ir::AttributeArg::SignatureTopic(_)
142                    | ir::AttributeArg::Anonymous
143                    | ir::AttributeArg::Name(_) => Ok(()),
144                    _ => Err(None),
145                }
146            },
147        )?;
148        if ink_attrs.is_anonymous() && ink_attrs.signature_topic().is_some() {
149            return Err(format_err_spanned!(
150                item_struct,
151                "cannot use use `anonymous` with `signature_topic`",
152            ));
153        }
154        Ok(Self {
155            item: syn::ItemStruct {
156                attrs: other_attrs,
157                ..item_struct
158            },
159            config: EventConfig::new(
160                ink_attrs.is_anonymous(),
161                ink_attrs.signature_topic(),
162                ink_attrs.name(),
163            ),
164        })
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn simple_try_from_works() {
174        let s = "11".repeat(32);
175        let item_struct: syn::ItemStruct = syn::parse_quote! {
176            #[ink(event)]
177            #[ink(signature_topic = #s)]
178            pub struct MyEvent {
179                #[ink(topic)]
180                field_1: i32,
181                field_2: bool,
182            }
183        };
184        assert!(Event::try_from(item_struct).is_ok());
185    }
186
187    fn assert_try_from_fails(item_struct: syn::ItemStruct, expected: &str) {
188        assert_eq!(
189            Event::try_from(item_struct).map_err(|err| err.to_string()),
190            Err(expected.to_string())
191        )
192    }
193
194    #[test]
195    fn conflicting_struct_attributes_fails() {
196        assert_try_from_fails(
197            syn::parse_quote! {
198                #[ink(event)]
199                #[ink(storage)]
200                pub struct MyEvent {
201                    #[ink(topic)]
202                    field_1: i32,
203                    field_2: bool,
204                }
205            },
206            "encountered conflicting ink! attribute argument",
207        )
208    }
209
210    #[test]
211    fn duplicate_struct_attributes_fails() {
212        assert_try_from_fails(
213            syn::parse_quote! {
214                #[ink(event)]
215                #[ink(event)]
216                pub struct MyEvent {
217                    #[ink(topic)]
218                    field_1: i32,
219                    field_2: bool,
220                }
221            },
222            "encountered duplicate ink! attribute",
223        );
224        assert_try_from_fails(
225            syn::parse_quote! {
226                #[ink(event)]
227                #[ink(anonymous)]
228                #[ink(anonymous)]
229                pub struct MyEvent {
230                    #[ink(topic)]
231                    field_1: i32,
232                    field_2: bool,
233                }
234            },
235            "encountered duplicate ink! attribute",
236        );
237        let s = "11".repeat(32);
238        assert_try_from_fails(
239            syn::parse_quote! {
240                #[ink(event)]
241                #[ink(signature_topic = #s)]
242                #[ink(signature_topic = #s)]
243                pub struct MyEvent {
244                    #[ink(topic)]
245                    field_1: i32,
246                    field_2: bool,
247                }
248            },
249            "encountered duplicate ink! attribute",
250        );
251    }
252
253    #[test]
254    fn wrong_first_struct_attribute_fails() {
255        assert_try_from_fails(
256            syn::parse_quote! {
257                #[ink(storage)]
258                #[ink(event)]
259                pub struct MyEvent {
260                    #[ink(topic)]
261                    field_1: i32,
262                    field_2: bool,
263                }
264            },
265            "unexpected first ink! attribute argument",
266        )
267    }
268
269    #[test]
270    fn missing_event_attribute_fails() {
271        assert_try_from_fails(
272            syn::parse_quote! {
273                pub struct MyEvent {
274                    #[ink(topic)]
275                    field_1: i32,
276                    field_2: bool,
277                }
278            },
279            "encountered unexpected empty expanded ink! attribute arguments",
280        )
281    }
282
283    #[test]
284    fn anonymous_event_works() {
285        fn assert_anonymous_event(event: syn::ItemStruct) {
286            match Event::try_from(event) {
287                Ok(event) => {
288                    assert!(event.anonymous());
289                }
290                Err(_) => panic!("encountered unexpected invalid anonymous event"),
291            }
292        }
293        assert_anonymous_event(syn::parse_quote! {
294            #[ink(event)]
295            #[ink(anonymous)]
296            pub struct MyEvent {
297                #[ink(topic)]
298                field_1: i32,
299                field_2: bool,
300            }
301        });
302        assert_anonymous_event(syn::parse_quote! {
303            #[ink(event, anonymous)]
304            pub struct MyEvent {
305                #[ink(topic)]
306                field_1: i32,
307                field_2: bool,
308            }
309        });
310    }
311    #[test]
312    fn signature_conflict_fails() {
313        let s = "11".repeat(32);
314        assert_try_from_fails(
315            syn::parse_quote! {
316                #[ink(event)]
317                #[ink(anonymous)]
318                #[ink(signature_topic = #s)]
319                pub struct MyEvent {
320                    #[ink(topic)]
321                    field_1: i32,
322                    field_2: bool,
323                }
324            },
325            "cannot use use `anonymous` with `signature_topic`",
326        )
327    }
328
329    #[test]
330    fn signature_invalid_length_fails() {
331        let s = "11".repeat(16);
332        assert_try_from_fails(
333            syn::parse_quote! {
334                #[ink(event)]
335                #[ink(signature_topic = #s)]
336                pub struct MyEvent {
337                    #[ink(topic)]
338                    field_1: i32,
339                    field_2: bool,
340                }
341            },
342            "`signature_topic` is expected to be 32-byte hex string. \
343                    Found 16 bytes",
344        )
345    }
346
347    #[test]
348    fn signature_invalid_hex_fails() {
349        let s = "XY".repeat(32);
350        assert_try_from_fails(
351            syn::parse_quote! {
352                #[ink(event)]
353                #[ink(signature_topic = #s)]
354                pub struct MyEvent {
355                    #[ink(topic)]
356                    field_1: i32,
357                    field_2: bool,
358                }
359            },
360            "`signature_topic` has invalid hex string",
361        )
362    }
363}