1mod 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#[derive(Debug, PartialEq, Eq)]
36pub struct Event {
37 item: syn::ItemStruct,
38 config: EventConfig,
39}
40
41impl Event {
42 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 pub fn item(&self) -> &syn::ItemStruct {
67 &self.item
68 }
69
70 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 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 pub fn anonymous(&self) -> bool {
98 self.config.anonymous()
99 }
100
101 pub fn signature_topic(&self) -> Option<SignatureTopic> {
107 self.config.signature_topic()
108 }
109
110 pub fn get_cfg_attrs(&self, span: Span) -> Vec<TokenStream2> {
112 extract_cfg_attributes(&self.item.attrs, span)
113 }
114
115 pub fn name(&self) -> Option<&str> {
117 self.config.name()
118 }
119}
120
121impl ToTokens for Event {
122 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}