1mod metadata;
16
17use ink_ir::{
18 format_err_spanned,
19 utils::duplicate_config_err,
20 SignatureTopicArg,
21};
22pub use metadata::event_metadata_derive;
23
24use ink_codegen::generate_code;
25use proc_macro2::TokenStream as TokenStream2;
26use quote::{
27 quote,
28 quote_spanned,
29};
30use syn::{
31 punctuated::Punctuated,
32 spanned::Spanned,
33 Token,
34};
35
36struct EventConfig {
38 pub anonymous: bool,
40 pub signature_topic: Option<SignatureTopicArg>,
42}
43
44impl EventConfig {
45 pub fn new(anonymous: bool, signature_topic: Option<SignatureTopicArg>) -> Self {
46 EventConfig {
47 anonymous,
48 signature_topic,
49 }
50 }
51}
52
53impl TryFrom<&[syn::Meta]> for EventConfig {
54 type Error = syn::Error;
55
56 fn try_from(args: &[syn::Meta]) -> Result<Self, Self::Error> {
57 let mut anonymous: Option<&syn::Meta> = None;
58 let mut signature_topic: Option<&syn::Meta> = None;
59 for arg in args.iter() {
60 if arg.path().is_ident("anonymous") {
61 if let Some(a_meta) = anonymous {
62 return Err(duplicate_config_err(a_meta, arg, "anonymous", "event"));
63 }
64 match arg {
65 syn::Meta::Path(_) => anonymous = Some(arg),
66 _ => {
67 return Err(format_err_spanned!(
68 arg,
69 "`#[ink(anonymous)]` takes no arguments",
70 ));
71 }
72 }
73 } else if arg.path().is_ident("signature_topic") {
74 if anonymous.is_some() {
75 return Err(format_err_spanned!(
76 arg,
77 "cannot specify `signature_topic` with `anonymous` in ink! event item configuration argument",
78 ));
79 }
80
81 if let Some(lit_str) = signature_topic {
82 return Err(duplicate_config_err(lit_str, arg, "anonymous", "event"));
83 }
84 match arg {
85 syn::Meta::NameValue(_) => signature_topic = Some(arg),
86 _ => {
87 return Err(format_err_spanned!(
88 arg,
89 "expected a name-value pair",
90 ));
91 }
92 }
93 } else {
94 return Err(format_err_spanned!(
95 arg,
96 "encountered unknown or unsupported ink! event item configuration argument",
97 ));
98 }
99 }
100
101 let signature_topic = if let Some(meta) = signature_topic {
102 Some(parse_signature_arg(meta.clone())?)
103 } else {
104 None
105 };
106
107 Ok(EventConfig::new(anonymous.is_some(), signature_topic))
108 }
109}
110
111pub fn generate(config: TokenStream2, input: TokenStream2) -> TokenStream2 {
114 ink_ir::Event::new(config, input)
115 .map(|event| generate_code(&event))
116 .unwrap_or_else(|err| err.to_compile_error())
117}
118
119pub fn event_derive(mut s: synstructure::Structure) -> TokenStream2 {
121 s.bind_with(|_| synstructure::BindStyle::Move)
122 .add_bounds(synstructure::AddBounds::Fields)
123 .underscore_const(true);
124 match &s.ast().data {
125 syn::Data::Struct(_) => {
126 event_derive_struct(s).unwrap_or_else(|err| err.to_compile_error())
127 }
128 _ => {
129 syn::Error::new(
130 s.ast().span(),
131 "can only derive `Event` for Rust `struct` items",
132 )
133 .to_compile_error()
134 }
135 }
136}
137
138#[allow(clippy::arithmetic_side_effects)] fn event_derive_struct(mut s: synstructure::Structure) -> syn::Result<TokenStream2> {
141 assert_eq!(s.variants().len(), 1, "can only operate on structs");
142
143 if !s.ast().generics.params.is_empty() {
144 return Err(syn::Error::new(
145 s.ast().generics.params.span(),
146 "can only derive `Event` for structs without generics",
147 ));
148 }
149
150 let span = s.ast().span();
151 let ink_attrs = parse_arg_attrs(&s.ast().attrs)?;
152 let config = EventConfig::try_from(ink_attrs.as_slice())?;
153 let anonymous = config.anonymous;
154
155 let mut topic_err: Option<syn::Error> = None;
157 s.variants_mut()[0].filter(|bi| {
158 match has_ink_topic_attribute(bi) {
159 Ok(has_attr) => has_attr,
160 Err(err) => {
161 match topic_err {
162 Some(ref mut topic_err) => topic_err.combine(err),
163 None => topic_err = Some(err),
164 }
165 false
166 }
167 }
168 });
169 if let Some(err) = topic_err {
170 return Err(err);
171 }
172
173 let variant = &s.variants()[0];
174
175 let anonymous_topics_offset = usize::from(!anonymous);
177 let len_topics = variant.bindings().len() + anonymous_topics_offset;
178
179 let remaining_topics_ty = match len_topics {
180 0 => quote_spanned!(span=> ::ink::env::event::state::NoRemainingTopics),
181 _ => {
182 quote_spanned!(span=> [::ink::env::event::state::HasRemainingTopics; #len_topics])
183 }
184 };
185
186 let event_signature_topic = if anonymous {
187 None
188 } else {
189 Some(quote_spanned!(span=>
190 .push_topic(Self::SIGNATURE_TOPIC.as_ref())
191 ))
192 };
193
194 let signature_topic = if !anonymous {
195 let event_ident = variant.ast().ident;
196 if let Some(sig_arg) = config.signature_topic {
197 let bytes = sig_arg.signature_topic();
198 quote_spanned!(span=> ::core::option::Option::Some([ #(#bytes),* ]))
199 } else {
200 let calculated_signature_topic =
201 signature_topic(variant.ast().fields, event_ident);
202 quote_spanned!(span=> ::core::option::Option::Some(#calculated_signature_topic))
203 }
204 } else {
205 quote_spanned!(span=> ::core::option::Option::None)
206 };
207
208 let topics = variant.bindings().iter().fold(quote!(), |acc, field| {
209 let field_ty = &field.ast().ty;
210 let field_span = field_ty.span();
211 quote_spanned!(field_span=>
212 #acc
213 .push_topic(::ink::as_option!(#field))
214 )
215 });
216 let pat = variant.pat();
217 let topics_builder = quote!(
218 #pat => {
219 builder
220 .build::<Self>()
221 #event_signature_topic
222 #topics
223 .finish()
224 }
225 );
226
227 Ok(s.bound_impl(quote!(::ink::env::Event), quote! {
228 type RemainingTopics = #remaining_topics_ty;
229 const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = #signature_topic;
230
231 fn topics<E, B>(
232 &self,
233 builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, E, B>,
234 ) -> <B as ::ink::env::event::TopicsBuilderBackend<E>>::Output
235 where
236 E: ::ink::env::Environment,
237 B: ::ink::env::event::TopicsBuilderBackend<E>,
238 {
239 match self {
240 #topics_builder
241 }
242 }
243 }))
244}
245
246fn has_ink_topic_attribute(field: &synstructure::BindingInfo) -> syn::Result<bool> {
252 let some_cfg_attrs = field
253 .ast()
254 .attrs
255 .iter()
256 .find(|attr| attr.path().is_ident("cfg"));
257 if some_cfg_attrs.is_some() {
258 Err(syn::Error::new(
259 field.ast().span(),
260 "conditional compilation is not allowed for event fields",
261 ))
262 } else {
263 let attrs = parse_arg_attrs(&field.ast().attrs)?;
264 has_ink_attribute(&attrs, "topic")
265 }
266}
267
268fn has_ink_attribute(ink_attrs: &[syn::Meta], path: &str) -> syn::Result<bool> {
270 let mut present = false;
271 for a in ink_attrs {
272 if a.path().is_ident(path) && !present {
273 present = true;
274 } else if a.path().is_ident(path) {
275 return Err(syn::Error::new(
276 a.span(),
277 format!("Only a single `#[ink({})]` is allowed", path),
278 ));
279 } else {
280 return Err(syn::Error::new(
281 a.span(),
282 "Unknown ink! attribute at this position".to_string(),
283 ));
284 }
285 }
286 Ok(present)
287}
288
289fn parse_arg_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<syn::Meta>> {
294 let mut ink_attrs = Vec::new();
295 for a in attrs {
296 if !a.path().is_ident("ink") {
297 continue;
298 }
299
300 let nested = a.parse_args_with(
301 Punctuated::<syn::Meta, Token![,]>::parse_separated_nonempty,
302 )?;
303 if nested.is_empty() {
304 return Err(syn::Error::new(
305 a.span(),
306 "Expected to have an argument".to_string(),
307 ));
308 }
309 ink_attrs.extend(nested.into_iter())
310 }
311
312 Ok(ink_attrs)
313}
314
315fn parse_signature_arg(meta: syn::Meta) -> syn::Result<SignatureTopicArg> {
322 if let syn::Meta::NameValue(nv) = &meta {
323 Ok(SignatureTopicArg::try_from(nv)?)
324 } else {
325 Err(syn::Error::new(
326 meta.span(),
327 "Expected to have an argument".to_string(),
328 ))
329 }
330}
331
332fn signature_topic(fields: &syn::Fields, event_ident: &syn::Ident) -> TokenStream2 {
336 let fields = fields
337 .iter()
338 .map(|field| {
339 quote::ToTokens::to_token_stream(&field.ty)
340 .to_string()
341 .replace(' ', "")
342 })
343 .collect::<Vec<_>>()
344 .join(",");
345 let topic_str = format!("{}({fields})", event_ident);
346 quote!(::ink::blake2x256!(#topic_str))
347}