1mod metadata;
16
17pub use metadata::event_metadata_derive;
18
19use ink_codegen::generate_code;
20use ink_ir::EventConfig;
21use ink_primitives::abi::Abi;
22use proc_macro2::TokenStream as TokenStream2;
23use quote::{
24 quote,
25 quote_spanned,
26};
27use syn::{
28 Token,
29 punctuated::Punctuated,
30 spanned::Spanned,
31};
32
33pub fn generate(config: TokenStream2, input: TokenStream2) -> TokenStream2 {
36 ink_ir::Event::new(config, input)
37 .map(|event| generate_code(&event))
38 .unwrap_or_else(|err| err.to_compile_error())
39}
40
41pub fn event_derive(mut s: synstructure::Structure) -> TokenStream2 {
43 s.bind_with(|_| synstructure::BindStyle::Move)
44 .add_bounds(synstructure::AddBounds::Fields)
45 .underscore_const(true);
46 match &s.ast().data {
47 syn::Data::Struct(_) => {
48 event_derive_struct(s).unwrap_or_else(|err| err.to_compile_error())
49 }
50 _ => {
51 syn::Error::new(
52 s.ast().span(),
53 "can only derive `Event` for Rust `struct` items",
54 )
55 .to_compile_error()
56 }
57 }
58}
59
60#[allow(clippy::arithmetic_side_effects)] fn event_derive_struct(s: synstructure::Structure) -> syn::Result<TokenStream2> {
63 assert_eq!(s.variants().len(), 1, "can only operate on structs");
64
65 if !s.ast().generics.params.is_empty() {
66 return Err(syn::Error::new(
67 s.ast().generics.params.span(),
68 "can only derive `Event` for structs without generics",
69 ));
70 }
71
72 let span = s.ast().span();
73 let config = EventConfig::try_from(s.ast().attrs.as_slice())?;
74 let anonymous = config.anonymous();
75 let variant = &s.variants()[0];
76
77 let mut topic_fields = Vec::new();
79 let mut data_fields = Vec::new();
80 let mut topic_err: Option<syn::Error> = None;
81 for field in variant.bindings() {
82 match has_ink_topic_attribute(field) {
83 Ok(is_topic) => {
84 if is_topic {
85 topic_fields.push(field);
86 } else {
87 data_fields.push(field);
88 }
89 }
90 Err(err) => {
91 match topic_err {
92 Some(ref mut topic_err) => topic_err.combine(err),
93 None => topic_err = Some(err),
94 }
95 }
96 }
97 }
98 if let Some(err) = topic_err {
99 return Err(err);
100 }
101
102 let anonymous_topics_offset = usize::from(!anonymous);
104 let len_topics = topic_fields.len() + anonymous_topics_offset;
105
106 if len_topics > 4 {
110 return Err(syn::Error::new(
111 span,
112 format!(
113 "Events{} can only have up to {} fields annotated with an \
114 `#[ink(topic)]` attribute",
115 if anonymous {
116 " with an `anonymous` attribute argument"
117 } else {
118 ""
119 },
120 if anonymous { 4 } else { 3 }
121 ),
122 ));
123 }
124
125 let remaining_topics_ty = match len_topics {
126 0 => quote_spanned!(span=> ::ink::env::event::state::NoRemainingTopics),
127 _ => {
128 quote_spanned!(span=> [::ink::env::event::state::HasRemainingTopics; #len_topics])
129 }
130 };
131
132 Ok(generate_abi_impls!(@type |abi| {
133 let abi_ty = match abi {
134 Abi::Ink => quote!(::ink::abi::Ink),
135 Abi::Sol => quote!(::ink::abi::Sol),
136 };
137
138 let event_signature_topic = if anonymous {
139 None
140 } else {
141 let value = match abi {
142 Abi::Ink => quote!(<Self as ::ink::env::Event<::ink::abi::Ink>>::SIGNATURE_TOPIC.as_ref()),
143 Abi::Sol => {
144 quote! {
145 ::ink::sol::FixedBytes::from_ref(
146 <Self as ::ink::env::Event<::ink::abi::Sol>>::SIGNATURE_TOPIC
147 .as_ref()
148 .expect("Expected a signature topic")
149 )
150 }
151 }
152 };
153 Some(quote_spanned!(span=>
154 .push_topic(#value)
155 ))
156 };
157
158 let signature_topic = if !anonymous {
159 let event_name = config
160 .name()
161 .map(ToString::to_string)
162 .unwrap_or_else(|| variant.ast().ident.to_string());
163 match abi {
164 Abi::Ink => {
165 if let Some(sig_arg) = config.signature_topic() {
166 let bytes = sig_arg.to_bytes();
167 quote_spanned!(span=> ::core::option::Option::Some([ #(#bytes),* ]))
168 } else {
169 let calculated_signature_topic =
170 signature_topic(variant.ast().fields, event_name);
171 quote_spanned!(span=> ::core::option::Option::Some(#calculated_signature_topic))
172 }
173 }
174 Abi::Sol => {
175 let calculated_signature_topic =
176 signature_topic_sol(variant.ast().fields, event_name);
177 quote_spanned!(span=> ::core::option::Option::Some(#calculated_signature_topic))
178 }
179 }
180 } else {
181 quote_spanned!(span=> ::core::option::Option::None)
182 };
183
184 let topics = topic_fields.iter().fold(quote!(), |acc, field| {
185 let field_ty = &field.ast().ty;
186 let field_span = field_ty.span();
187 let value = match abi {
188 Abi::Ink => quote!(::ink::as_option!(#field)),
189 Abi::Sol => quote!(#field),
190 };
191 quote_spanned!(field_span=>
192 #acc
193 .push_topic(#value)
194 )
195 });
196 let pat = variant.pat();
197 let topics_builder = quote!(
198 #pat => {
199 builder
200 .build::<Self>()
201 #event_signature_topic
202 #topics
203 .finish()
204 }
205 );
206
207 let encode_data = match abi {
208 Abi::Ink => quote! {
209 ::ink::abi::AbiEncodeWith::<::ink::abi::Ink>::encode_with(self)
210 },
211 Abi::Sol => {
212 let data_field_tys = data_fields.iter().map(|field| {
214 let ty = &field.ast().ty;
215 quote!( &#ty )
216 });
217 let data_field_values = data_fields.iter().map(|field| {
218 &field.binding
219 });
220 quote! {
221 match self {
222 #pat => {
223 ::ink::sol::encode_sequence::<( #( #data_field_tys, )* )>(
224 &( #( #data_field_values, )* ),
225 )
226 }
227 }
228 }
229 },
230 };
231
232 s.bound_impl(quote!(::ink::env::Event<#abi_ty>), quote! {
233 type RemainingTopics = #remaining_topics_ty;
234 const SIGNATURE_TOPIC: ::core::option::Option<[::core::primitive::u8; 32]> = #signature_topic;
235
236 fn topics<B>(
237 &self,
238 builder: ::ink::env::event::TopicsBuilder<::ink::env::event::state::Uninit, B, #abi_ty>,
239 ) -> <B as ::ink::env::event::TopicsBuilderBackend<#abi_ty>>::Output
240 where
241 B: ::ink::env::event::TopicsBuilderBackend<#abi_ty>,
242 {
243 match self {
244 #topics_builder
245 }
246 }
247
248 fn encode_data(&self) -> ::ink::prelude::vec::Vec<::core::primitive::u8> {
249 #encode_data
250 }
251 })
252 }))
253}
254
255fn has_ink_topic_attribute(field: &synstructure::BindingInfo) -> syn::Result<bool> {
261 let some_cfg_attrs = field
262 .ast()
263 .attrs
264 .iter()
265 .find(|attr| attr.path().is_ident("cfg"));
266 if some_cfg_attrs.is_some() {
267 Err(syn::Error::new(
268 field.ast().span(),
269 "conditional compilation is not allowed for event fields",
270 ))
271 } else {
272 let attrs = parse_arg_attrs(&field.ast().attrs)?;
273 has_ink_attribute(&attrs, "topic")
274 }
275}
276
277fn has_ink_attribute(ink_attrs: &[syn::Meta], path: &str) -> syn::Result<bool> {
279 let mut present = false;
280 for a in ink_attrs {
281 if a.path().is_ident(path) && !present {
282 present = true;
283 } else if a.path().is_ident(path) {
284 return Err(syn::Error::new(
285 a.span(),
286 format!("Only a single `#[ink({path})]` is allowed"),
287 ));
288 } else {
289 return Err(syn::Error::new(
290 a.span(),
291 "Unknown ink! attribute at this position",
292 ));
293 }
294 }
295 Ok(present)
296}
297
298fn parse_arg_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<syn::Meta>> {
303 let mut ink_attrs = Vec::new();
304 for a in attrs {
305 if !a.path().is_ident("ink") {
306 continue;
307 }
308
309 let nested = a.parse_args_with(
310 Punctuated::<syn::Meta, Token![,]>::parse_separated_nonempty,
311 )?;
312 if nested.is_empty() {
313 return Err(syn::Error::new(
314 a.span(),
315 "Expected to have an argument".to_string(),
316 ));
317 }
318 ink_attrs.extend(nested.into_iter())
319 }
320
321 Ok(ink_attrs)
322}
323
324fn signature_topic(fields: &syn::Fields, event_name: String) -> TokenStream2 {
328 let fields = fields
329 .iter()
330 .map(|field| {
331 quote::ToTokens::to_token_stream(&field.ty)
332 .to_string()
333 .replace(' ', "")
334 })
335 .collect::<Vec<_>>()
336 .join(",");
337 let topic_str = format!("{event_name}({fields})");
338 quote!(::ink::blake2x256!(#topic_str))
339}
340
341fn signature_topic_sol(fields: &syn::Fields, event_name: String) -> TokenStream2 {
345 let param_tys = fields.iter().map(|field| {
346 let ty = &field.ty;
347 quote! {
348 <#ty as ::ink::SolEncode>::SOL_NAME
349 }
350 });
351 let sig_arg_fmt_params = (0..fields.len())
352 .map(|_| "{}")
353 .collect::<Vec<_>>()
354 .join(",");
355 let sig_fmt_str = format!("{{}}({sig_arg_fmt_params})");
356 let sig_str = quote! {
357 ::ink::codegen::utils::const_format!(#sig_fmt_str, #event_name #(,#param_tys)*)
358 };
359 quote!(::ink::keccak_256!(#sig_str))
360}