unc_sdk_macros/
lib.rs

1#![recursion_limit = "128"]
2extern crate proc_macro;
3
4mod core_impl;
5
6use core_impl::{ext::generate_ext_structs, metadata::generate_contract_metadata_method};
7
8use proc_macro::TokenStream;
9
10use self::core_impl::*;
11use darling::ast::NestedMeta;
12use darling::{Error, FromMeta};
13use proc_macro2::{Ident, Span};
14use quote::{quote, ToTokens};
15use syn::{parse_quote, Expr, ImplItem, ItemEnum, ItemImpl, ItemStruct, ItemTrait, WhereClause};
16
17#[derive(Debug, Clone)]
18struct Serializers {
19    vec: Vec<Expr>,
20}
21
22impl FromMeta for Serializers {
23    fn from_expr(expr: &syn::Expr) -> Result<Self, darling::Error> {
24        match expr {
25            syn::Expr::Array(expr_array) => Ok(Serializers {
26                vec: expr_array
27                    .elems
28                    .iter()
29                    .map(<Expr as FromMeta>::from_expr)
30                    .map(|x| x.unwrap())
31                    .collect::<Vec<_>>(),
32            }),
33            _ => Err(Error::unexpected_expr_type(expr)),
34        }
35    }
36}
37
38#[derive(FromMeta)]
39struct UncMacroArgs {
40    serializers: Option<Serializers>,
41    contract_state: Option<bool>,
42    contract_metadata: Option<core_impl::ContractMetadata>,
43    inside_uncsdk: Option<bool>,
44}
45
46/// This attribute macro is used to enhance the unc_bindgen macro.
47/// It is used to add Borsh and Serde derives for serialization and deserialization.
48/// It also adds `BorshSchema` and `JsonSchema` if needed
49///
50/// If you would like to add Borsh or Serde serialization and deserialization to your contract,
51/// you can use the abi attribute and pass in the serializers you would like to use.
52///
53/// # Example
54///
55/// ```ignore
56/// #[unc(serializers=[borsh, json])]
57/// struct MyStruct {
58///    pub name: String,
59/// }
60/// ```
61/// effectively becomes:
62/// ```ignore
63/// use borsh::{BorshSerialize, BorshDeserialize};
64/// use serde::{Serialize, Deserialize};
65/// use unc_sdk_macro::UncSchema;
66/// #[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, UncSchema)]
67/// #[borsh(crate = "unc_sdk::borsh")]
68/// #[serde(crate = "unc_sdk::serde")]
69/// struct MyStruct {
70///   pub name: String,
71/// }
72/// ```
73/// Please note that `BorshSchema` and `JsonSchema` are added inside UncSchema whenever you use unc macro for struct or enum.
74/// By default, if no serializers are passed, Borsh is used.
75///
76/// If you want this struct to be a contract state, you can pass in the contract_state argument.
77///
78/// # Example
79/// ```ignore
80/// #[unc(contract_state)]
81/// struct MyStruct {
82///     pub name: String,
83/// }
84/// ```
85/// becomes:
86/// ```ignore
87/// #[unc_bindgen]
88/// #[derive(BorshSerialize, BorshDeserialize, UncSchema)]
89/// #[borsh(crate = "unc_sdk::borsh")]
90/// struct MyStruct {
91///    pub name: String,
92/// }
93/// ```
94///
95/// As well, the macro supports arguments like `event_json` and `contract_metadata`.
96///
97#[proc_macro_attribute]
98pub fn unc(attr: TokenStream, item: TokenStream) -> TokenStream {
99    if attr.to_string().contains("event_json") {
100        return core_impl::unc_events(attr, item);
101    }
102
103    let meta_list = match NestedMeta::parse_meta_list(attr.into()) {
104        Ok(v) => v,
105        Err(e) => {
106            return TokenStream::from(Error::from(e).write_errors());
107        }
108    };
109
110    let unc_macro_args = match UncMacroArgs::from_list(&meta_list) {
111        Ok(v) => v,
112        Err(e) => {
113            return TokenStream::from(e.write_errors());
114        }
115    };
116
117    let unc_sdk_crate = if unc_macro_args.inside_uncsdk.unwrap_or(false) {
118        quote! {crate}
119    } else {
120        quote! {::unc_sdk}
121    };
122    let string_borsh_crate = quote! {#unc_sdk_crate::borsh}.to_string();
123    let string_serde_crate = quote! {#unc_sdk_crate::serde}.to_string();
124
125    let mut expanded: proc_macro2::TokenStream = quote! {};
126
127    if unc_macro_args.contract_state.unwrap_or(false) {
128        if let Some(metadata) = unc_macro_args.contract_metadata {
129            expanded = quote! {#[#unc_sdk_crate::unc_bindgen(#metadata)]}
130        } else {
131            expanded = quote! {#[#unc_sdk_crate::unc_bindgen]}
132        }
133    };
134
135    let mut has_borsh = false;
136    let mut has_json = false;
137
138    let mut borsh_attr = quote! {};
139
140    match unc_macro_args.serializers {
141        Some(serializers) => {
142            let attr2 = serializers.clone();
143
144            attr2.vec.iter().for_each(|old_expr| {
145                let new_expr = &mut old_expr.clone();
146                match &mut *new_expr {
147                    Expr::Call(ref mut call_expr) => {
148                        if let Expr::Path(ref mut path) = &mut *call_expr.func {
149                            if let Some(ident) = path.path.get_ident() {
150                                if *ident == "json" {
151                                    has_json = true;
152                                    path.path =
153                                        syn::Path::from(Ident::new("serde", Span::call_site()));
154                                    call_expr.args.push(parse_quote! {crate=#string_serde_crate});
155                                } else if *ident == "borsh" {
156                                    has_borsh = true;
157                                    call_expr.args.push(parse_quote! {crate=#string_borsh_crate});
158                                }
159                            }
160                        }
161                        borsh_attr = quote! {#[#new_expr]};
162                    }
163                    Expr::Path(ref mut path_expr) => {
164                        if let Some(ident) = path_expr.path.get_ident() {
165                            if *ident == "json" {
166                                has_json = true;
167                            }
168                            if *ident == "borsh" {
169                                has_borsh = true;
170                                borsh_attr = quote! {#[borsh(crate=#string_borsh_crate)]};
171                            }
172                        }
173                    }
174                    _ => {}
175                }
176            });
177        }
178        None => {
179            has_borsh = true;
180            borsh_attr = quote! {#[borsh(crate = #string_borsh_crate)]};
181        }
182    }
183
184    #[cfg(feature = "abi")]
185    {
186        let schema_derive: proc_macro2::TokenStream =
187            get_schema_derive(has_json, has_borsh, unc_sdk_crate.clone(), false);
188        expanded = quote! {
189            #expanded
190            #schema_derive
191        };
192    }
193
194    if has_borsh {
195        expanded = quote! {
196            #expanded
197            #[derive(#unc_sdk_crate::borsh::BorshSerialize, #unc_sdk_crate::borsh::BorshDeserialize)]
198            #borsh_attr
199        };
200    }
201
202    if has_json {
203        expanded = quote! {
204            #expanded
205            #[derive(#unc_sdk_crate::serde::Serialize, #unc_sdk_crate::serde::Deserialize)]
206            #[serde(crate = #string_serde_crate)]
207        };
208    }
209
210    if let Ok(input) = syn::parse::<ItemStruct>(item.clone()) {
211        expanded = quote! {
212            #expanded
213            #input
214        };
215    } else if let Ok(input) = syn::parse::<ItemEnum>(item.clone()) {
216        expanded = quote! {
217            #expanded
218            #input
219        };
220    } else if let Ok(input) = syn::parse::<ItemImpl>(item) {
221        expanded = quote! {
222            #[#unc_sdk_crate::unc_bindgen]
223            #input
224        };
225    } else {
226        return TokenStream::from(
227            syn::Error::new(
228                Span::call_site(),
229                "unc macro can only be used on struct or enum definition and impl sections.",
230            )
231            .to_compile_error(),
232        );
233    }
234
235    TokenStream::from(expanded)
236}
237
238/// This attribute macro is used on a struct and its implementations
239/// to generate the necessary code to expose `pub` methods from the contract as well
240/// as generating the glue code to be a valid UNC contract.
241///
242/// This macro will generate code to load and deserialize state if the `self` parameter is included
243/// as well as saving it back to state if `&mut self` is used.
244///
245/// For parameter serialization, this macro will generate a struct with all of the parameters as
246/// fields and derive deserialization for it. By default this will be JSON deserialized with `serde`
247/// but can be overwritten by using `#[serializer(borsh)]`.
248///
249/// `#[unc_bindgen]` will also handle serializing and setting the return value of the
250/// function execution based on what type is returned by the function. By default, this will be
251/// done through `serde` serialized as JSON, but this can be overwritten using
252/// `#[result_serializer(borsh)]`.
253///
254/// # Examples
255///
256/// ```ignore
257/// use unc_sdk::unc_bindgen;
258///
259/// #[unc_bindgen]
260/// pub struct Contract {
261///    data: i8,
262/// }
263///
264/// #[unc_bindgen]
265/// impl Contract {
266///     pub fn some_function(&self) {}
267/// }
268/// ```
269///
270/// Events Standard:
271///
272/// By passing `event_json` as an argument `unc_bindgen` will generate the relevant code to format events
273/// according to UIP-297
274///
275/// For parameter serialization, this macro will generate a wrapper struct to include the UIP-297 standard fields `standard` and `version
276/// as well as include serialization reformatting to include the `event` and `data` fields automatically.
277/// The `standard` and `version` values must be included in the enum and variant declaration (see example below).
278/// By default this will be JSON deserialized with `serde`
279///
280///
281/// # Examples
282///
283/// ```ignore
284/// use unc_sdk::unc_bindgen;
285///
286/// #[unc_bindgen(event_json(standard = "uipXXX"))]
287/// pub enum MyEvents {
288///    #[event_version("1.0.0")]
289///    Swap { token_in: AccountId, token_out: AccountId, amount_in: u128, amount_out: u128 },
290///
291///    #[event_version("2.0.0")]
292///    StringEvent(String),
293///
294///    #[event_version("2.1.0")]
295///    EmptyEvent
296/// }
297///
298/// #[unc_bindgen]
299/// impl Contract {
300///     pub fn some_function(&self) {
301///         MyEvents::StringEvent(
302///             String::from("some_string")
303///         ).emit();
304///     }
305///
306/// }
307/// ```
308///
309/// Contract Source Metadata Standard:
310///
311/// By using `contract_metadata` as an argument `unc_bindgen` will populate the contract metadata
312/// according to [`UIP-330`](<https://github.com/utnet-org/UIPs/blob/master/uips/uip-0330.md>) standard. This still applies even when `#[unc_bindgen]` is used without
313/// any arguments.
314///
315/// All fields(version, link, standard) are optional and will be populated with defaults from the Cargo.toml file if not specified.
316///
317/// The `contract_source_metadata()` view function will be added and can be used to retrieve the source metadata.
318/// Also, the source metadata will be stored as a constant, `CONTRACT_SOURCE_METADATA`, in the contract code.
319///
320/// # Examples
321/// ```ignore
322/// use unc_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
323/// use unc_sdk::unc_bindgen;
324///
325/// #[derive(Default, BorshSerialize, BorshDeserialize)]
326/// #[unc_bindgen(contract_metadata(
327///     version = "39f2d2646f2f60e18ab53337501370dc02a5661c",
328///     link = "https://github.com/utnet-org/examples/nft-tutorial",
329///     standard(standard = "uip330", version = "2.0.0"),
330///     standard(standard = "uip171", version = "1.0.0"),
331///     standard(standard = "uip177", version = "1.2.0"),
332/// ))]
333/// struct Contract {}
334/// ```
335#[proc_macro_attribute]
336pub fn unc_bindgen(attr: TokenStream, item: TokenStream) -> TokenStream {
337    if attr.to_string().contains("event_json") {
338        return core_impl::unc_events(attr, item);
339    }
340
341    let generate_metadata = |ident: &Ident,
342                             generics: &syn::Generics|
343     -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
344        let metadata_impl_gen = generate_contract_metadata_method(ident, generics).into();
345        let metadata_impl_gen = syn::parse::<ItemImpl>(metadata_impl_gen)
346            .expect("failed to generate contract metadata");
347        process_impl_block(metadata_impl_gen)
348    };
349
350    if let Ok(input) = syn::parse::<ItemStruct>(item.clone()) {
351        let metadata = core_impl::contract_source_metadata_const(attr);
352
353        let metadata_impl_gen = generate_metadata(&input.ident, &input.generics);
354
355        let metadata_impl_gen = match metadata_impl_gen {
356            Ok(metadata) => metadata,
357            Err(err) => return err.into(),
358        };
359
360        let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics));
361        #[cfg(feature = "__abi-embed-checked")]
362        let abi_embedded = abi::embed();
363        #[cfg(not(feature = "__abi-embed-checked"))]
364        let abi_embedded = quote! {};
365        TokenStream::from(quote! {
366            #input
367            #ext_gen
368            #abi_embedded
369            #metadata
370            #metadata_impl_gen
371        })
372    } else if let Ok(input) = syn::parse::<ItemEnum>(item.clone()) {
373        let metadata = core_impl::contract_source_metadata_const(attr);
374        let metadata_impl_gen = generate_metadata(&input.ident, &input.generics);
375
376        let metadata_impl_gen = match metadata_impl_gen {
377            Ok(metadata) => metadata,
378            Err(err) => return err.into(),
379        };
380
381        let ext_gen = generate_ext_structs(&input.ident, Some(&input.generics));
382        #[cfg(feature = "__abi-embed-checked")]
383        let abi_embedded = abi::embed();
384        #[cfg(not(feature = "__abi-embed-checked"))]
385        let abi_embedded = quote! {};
386        TokenStream::from(quote! {
387            #input
388            #ext_gen
389            #abi_embedded
390            #metadata
391            #metadata_impl_gen
392        })
393    } else if let Ok(input) = syn::parse::<ItemImpl>(item) {
394        for method in &input.items {
395            if let ImplItem::Fn(m) = method {
396                let ident = &m.sig.ident;
397                if ident.eq("__contract_abi") || ident.eq("contract_source_metadata") {
398                    return TokenStream::from(
399                        syn::Error::new_spanned(
400                            ident.to_token_stream(),
401                            "use of reserved contract method",
402                        )
403                        .to_compile_error(),
404                    );
405                }
406            }
407        }
408        match process_impl_block(input) {
409            Ok(output) => output,
410            Err(output) => output,
411        }
412        .into()
413    } else {
414        TokenStream::from(
415            syn::Error::new(
416                Span::call_site(),
417                "unc_bindgen can only be used on struct or enum definition and impl sections.",
418            )
419            .to_compile_error(),
420        )
421    }
422}
423
424// This function deals with impl block processing, generating wrappers and ABI.
425//
426// # Arguments
427// * input - impl block to process.
428//
429// The Result has a TokenStream error type, because those need to be propagated to the compiler.
430fn process_impl_block(
431    mut input: ItemImpl,
432) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
433    let item_impl_info = match ItemImplInfo::new(&mut input) {
434        Ok(x) => x,
435        Err(err) => return Err(err.to_compile_error()),
436    };
437
438    #[cfg(not(feature = "__abi-generate"))]
439    let abi_generated = quote! {};
440    #[cfg(feature = "__abi-generate")]
441    let abi_generated = abi::generate(&item_impl_info);
442
443    let generated_code = item_impl_info.wrapper_code();
444
445    // Add wrapper methods for ext call API
446    let ext_generated_code = item_impl_info.generate_ext_wrapper_code();
447
448    Ok(TokenStream::from(quote! {
449        #ext_generated_code
450        #input
451        #generated_code
452        #abi_generated
453    })
454    .into())
455}
456
457/// `ext_contract` takes a Rust Trait and converts it to a module with static methods.
458/// Each of these static methods takes positional arguments defined by the Trait,
459/// then the receiver_id, the attached deposit and the amount of gas and returns a new Promise.
460///
461/// # Examples
462///
463/// ```ignore
464/// use unc_sdk::ext_contract;
465///
466/// #[ext_contract(ext_calculator)]
467/// trait Calculator {
468///     fn mult(&self, a: u64, b: u64) -> u128;
469///     fn sum(&self, a: u128, b: u128) -> u128;
470/// }
471/// ```
472#[proc_macro_attribute]
473pub fn ext_contract(attr: TokenStream, item: TokenStream) -> TokenStream {
474    if let Ok(mut input) = syn::parse::<ItemTrait>(item) {
475        let mod_name: Option<proc_macro2::Ident> = if attr.is_empty() {
476            None
477        } else {
478            match syn::parse(attr) {
479                Ok(x) => x,
480                Err(err) => {
481                    return TokenStream::from(
482                        syn::Error::new(
483                            Span::call_site(),
484                            format!("Failed to parse mod name for ext_contract: {}", err),
485                        )
486                        .to_compile_error(),
487                    )
488                }
489            }
490        };
491        let item_trait_info = match ItemTraitInfo::new(&mut input, mod_name) {
492            Ok(x) => x,
493            Err(err) => return TokenStream::from(err.to_compile_error()),
494        };
495        let ext_api = item_trait_info.wrap_trait_ext();
496
497        TokenStream::from(quote! {
498            #input
499            #ext_api
500        })
501    } else {
502        TokenStream::from(
503            syn::Error::new(Span::call_site(), "ext_contract can only be used on traits")
504                .to_compile_error(),
505        )
506    }
507}
508
509// The below attributes a marker-attributes and therefore they are no-op.
510
511/// `callback` is a marker attribute it does not generate code by itself.
512#[proc_macro_attribute]
513#[deprecated(since = "2.0.0", note = "Case is handled internally by macro, no need to import")]
514pub fn callback(_attr: TokenStream, item: TokenStream) -> TokenStream {
515    item
516}
517
518/// `callback_args_vec` is a marker attribute it does not generate code by itself.
519#[deprecated(since = "2.0.0", note = "Case is handled internally by macro, no need to import")]
520#[proc_macro_attribute]
521pub fn callback_vec(_attr: TokenStream, item: TokenStream) -> TokenStream {
522    item
523}
524
525/// `serializer` is a marker attribute it does not generate code by itself.
526#[deprecated(since = "2.0.0", note = "Case is handled internally by macro, no need to import")]
527#[proc_macro_attribute]
528pub fn serializer(_attr: TokenStream, item: TokenStream) -> TokenStream {
529    item
530}
531
532/// `result_serializer` is a marker attribute it does not generate code by itself.
533#[deprecated(since = "2.0.0", note = "Case is handled internally by macro, no need to import")]
534#[proc_macro_attribute]
535pub fn result_serializer(_attr: TokenStream, item: TokenStream) -> TokenStream {
536    item
537}
538
539/// `init` is a marker attribute it does not generate code by itself.
540#[deprecated(since = "2.0.0", note = "Case is handled internally by macro, no need to import")]
541#[proc_macro_attribute]
542pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream {
543    item
544}
545
546#[cfg(feature = "abi")]
547#[derive(darling::FromDeriveInput, Debug)]
548#[darling(attributes(abi), forward_attrs(serde, borsh_skip, schemars, validate))]
549struct DeriveUncSchema {
550    attrs: Vec<syn::Attribute>,
551    json: Option<bool>,
552    borsh: Option<bool>,
553}
554
555#[proc_macro_derive(UncSchema, attributes(abi, serde, borsh, schemars, validate, inside_uncsdk))]
556pub fn derive_unc_schema(#[allow(unused)] input: TokenStream) -> TokenStream {
557    #[cfg(not(feature = "abi"))]
558    {
559        TokenStream::from(quote! {})
560    }
561
562    #[cfg(feature = "abi")]
563    {
564        use darling::FromDeriveInput;
565
566        let derive_input = syn::parse_macro_input!(input as syn::DeriveInput);
567        let generics = derive_input.generics.clone();
568        let args = match DeriveUncSchema::from_derive_input(&derive_input) {
569            Ok(v) => v,
570            Err(e) => {
571                return TokenStream::from(e.write_errors());
572            }
573        };
574
575        if args.borsh.is_none()
576            && args.json.is_none()
577            && derive_input.clone().attrs.iter().any(|attr| attr.path().is_ident("abi"))
578        {
579            return TokenStream::from(
580                syn::Error::new_spanned(
581                    derive_input.to_token_stream(),
582                    "At least one of `json` or `borsh` inside of `#[abi(...)]` must be specified",
583                )
584                .to_compile_error(),
585            );
586        }
587
588        // #[abi(json, borsh)]
589        let (json_schema, borsh_schema) = (args.json.unwrap_or(false), args.borsh.unwrap_or(false));
590        let mut input = derive_input.clone();
591        input.attrs = args.attrs;
592
593        let strip_unknown_attr = |attrs: &mut Vec<syn::Attribute>| {
594            attrs.retain(|attr| {
595                ["serde", "schemars", "validate", "borsh"]
596                    .iter()
597                    .any(|&path| attr.path().is_ident(path))
598            });
599        };
600
601        match &mut input.data {
602            syn::Data::Struct(data) => {
603                for field in &mut data.fields {
604                    strip_unknown_attr(&mut field.attrs);
605                }
606            }
607            syn::Data::Enum(data) => {
608                for variant in &mut data.variants {
609                    strip_unknown_attr(&mut variant.attrs);
610                    for field in &mut variant.fields {
611                        strip_unknown_attr(&mut field.attrs);
612                    }
613                }
614            }
615            syn::Data::Union(_) => {
616                return TokenStream::from(
617                    syn::Error::new_spanned(
618                        input.to_token_stream(),
619                        "`UncSchema` does not support derive for unions",
620                    )
621                    .to_compile_error(),
622                )
623            }
624        }
625
626        let unc_sdk_crate =
627            if derive_input.attrs.iter().any(|attr| attr.path().is_ident("inside_uncsdk")) {
628                quote! {crate}
629            } else {
630                quote! {::unc_sdk}
631            };
632
633        // <unspecified> or #[abi(json)]
634        let json_schema = json_schema || !borsh_schema;
635
636        let derive = get_schema_derive(json_schema, borsh_schema, unc_sdk_crate.clone(), true);
637
638        let input_ident = &input.ident;
639
640        let input_ident_proxy = quote::format_ident!("{}__UNC_SCHEMA_PROXY", input_ident);
641
642        let json_impl = if json_schema {
643            let where_clause = get_where_clause(
644                &generics,
645                input_ident,
646                quote! {#unc_sdk_crate::schemars::JsonSchema},
647            );
648            quote! {
649                #[automatically_derived]
650                impl #generics #unc_sdk_crate::schemars::JsonSchema for #input_ident_proxy #generics #where_clause {
651                    fn schema_name() -> ::std::string::String {
652                        stringify!(#input_ident #generics).to_string()
653                    }
654
655                    fn json_schema(gen: &mut #unc_sdk_crate::schemars::gen::SchemaGenerator) -> #unc_sdk_crate::schemars::schema::Schema {
656                        <#input_ident #generics as #unc_sdk_crate::schemars::JsonSchema>::json_schema(gen)
657                    }
658                }
659            }
660        } else {
661            quote! {}
662        };
663
664        let borsh_impl = if borsh_schema {
665            let where_clause = get_where_clause(
666                &generics,
667                input_ident,
668                quote! {#unc_sdk_crate::borsh::BorshSchema},
669            );
670            quote! {
671                #[automatically_derived]
672                impl #generics #unc_sdk_crate::borsh::BorshSchema for #input_ident_proxy #generics #where_clause {
673                    fn declaration() -> #unc_sdk_crate::borsh::schema::Declaration {
674                        stringify!(#input_ident #generics).to_string()
675                    }
676
677                    fn add_definitions_recursively(
678                        definitions: &mut #unc_sdk_crate::borsh::__private::maybestd::collections::BTreeMap<
679                            #unc_sdk_crate::borsh::schema::Declaration,
680                            #unc_sdk_crate::borsh::schema::Definition
681                        >,
682                    ) {
683                        <#input_ident #generics as #unc_sdk_crate::borsh::BorshSchema>::add_definitions_recursively(definitions);
684                    }
685                }
686            }
687        } else {
688            quote! {}
689        };
690
691        TokenStream::from(quote! {
692            #[cfg(not(target_arch = "wasm32"))]
693            const _: () = {
694                #[allow(non_camel_case_types)]
695                type #input_ident_proxy #generics = #input_ident #generics;
696                {
697                    #derive
698                    #[allow(dead_code)]
699                    #input
700
701                    #json_impl
702                    #borsh_impl
703                };
704            };
705        })
706    }
707}
708
709#[allow(dead_code)]
710fn get_schema_derive(
711    json_schema: bool,
712    borsh_schema: bool,
713    unc_sdk_crate: proc_macro2::TokenStream,
714    need_borsh_crate: bool,
715) -> proc_macro2::TokenStream {
716    let string_borsh_crate = quote! {#unc_sdk_crate::borsh}.to_string();
717    let string_schemars_crate = quote! {#unc_sdk_crate::schemars}.to_string();
718
719    let mut derive = quote! {};
720    if borsh_schema {
721        derive = quote! {
722            #[cfg_attr(not(target_arch = "wasm32"), derive(#unc_sdk_crate::borsh::BorshSchema))]
723        };
724        if need_borsh_crate {
725            derive = quote! {
726                #derive
727                #[cfg_attr(not(target_arch = "wasm32"), borsh(crate = #string_borsh_crate))]
728            };
729        }
730    }
731    if json_schema {
732        derive = quote! {
733            #derive
734            #[cfg_attr(not(target_arch = "wasm32"), derive(#unc_sdk_crate::schemars::JsonSchema))]
735            #[cfg_attr(not(target_arch = "wasm32"), schemars(crate = #string_schemars_crate))]
736        };
737    }
738    derive
739}
740
741#[cfg(feature = "abi")]
742fn get_where_clause(
743    generics: &syn::Generics,
744    input_ident: &syn::Ident,
745    trait_name: proc_macro2::TokenStream,
746) -> WhereClause {
747    let (_, ty_generics, where_clause) = generics.split_for_impl();
748
749    let predicate = parse_quote!(#input_ident #ty_generics: #trait_name);
750
751    let where_clause: WhereClause = if let Some(mut w) = where_clause.cloned() {
752        w.predicates.push(predicate);
753        w
754    } else {
755        parse_quote!(where #predicate)
756    };
757    where_clause
758}
759
760/// `PanicOnDefault` generates implementation for `Default` trait that panics with the following
761/// message `The contract is not initialized` when `default()` is called.
762/// This is a helpful macro in case the contract is required to be initialized with either `init` or
763/// `init(ignore_state)`.
764#[proc_macro_derive(PanicOnDefault)]
765pub fn derive_no_default(item: TokenStream) -> TokenStream {
766    if let Ok(input) = syn::parse::<ItemStruct>(item) {
767        let name = &input.ident;
768        TokenStream::from(quote! {
769            impl ::std::default::Default for #name {
770                fn default() -> Self {
771                    ::unc_sdk::env::panic_str("The contract is not initialized");
772                }
773            }
774        })
775    } else {
776        TokenStream::from(
777            syn::Error::new(
778                Span::call_site(),
779                "PanicOnDefault can only be used on type declarations sections.",
780            )
781            .to_compile_error(),
782        )
783    }
784}
785
786/// `BorshStorageKey` generates implementation for `BorshIntoStorageKey` trait.
787/// It allows the type to be passed as a unique prefix for persistent collections.
788/// The type should also implement or derive `BorshSerialize` trait.
789#[proc_macro_derive(BorshStorageKey)]
790pub fn borsh_storage_key(item: TokenStream) -> TokenStream {
791    let (name, generics) = if let Ok(input) = syn::parse::<ItemEnum>(item.clone()) {
792        (input.ident, input.generics)
793    } else if let Ok(input) = syn::parse::<ItemStruct>(item) {
794        (input.ident, input.generics)
795    } else {
796        return TokenStream::from(
797            syn::Error::new(
798                Span::call_site(),
799                "BorshStorageKey can only be used as a derive on enums or structs.",
800            )
801            .to_compile_error(),
802        );
803    };
804    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
805    let predicate = parse_quote!(#name #ty_generics: ::unc_sdk::borsh::BorshSerialize);
806    let where_clause: WhereClause = if let Some(mut w) = where_clause.cloned() {
807        w.predicates.push(predicate);
808        w
809    } else {
810        parse_quote!(where #predicate)
811    };
812    TokenStream::from(quote! {
813        impl #impl_generics ::unc_sdk::__private::BorshIntoStorageKey for #name #ty_generics #where_clause {}
814    })
815}
816
817/// `FunctionError` generates implementation for `unc_sdk::FunctionError` trait.
818/// It allows contract runtime to panic with the type using its `ToString` implementation
819/// as the message.
820#[proc_macro_derive(FunctionError)]
821pub fn function_error(item: TokenStream) -> TokenStream {
822    let name = if let Ok(input) = syn::parse::<ItemEnum>(item.clone()) {
823        input.ident
824    } else if let Ok(input) = syn::parse::<ItemStruct>(item) {
825        input.ident
826    } else {
827        return TokenStream::from(
828            syn::Error::new(
829                Span::call_site(),
830                "FunctionError can only be used as a derive on enums or structs.",
831            )
832            .to_compile_error(),
833        );
834    };
835    TokenStream::from(quote! {
836        impl ::unc_sdk::FunctionError for #name {
837            fn panic(&self) -> ! {
838                ::unc_sdk::env::panic_str(&::std::string::ToString::to_string(&self))
839            }
840        }
841    })
842}
843
844/// NOTE: This is an internal implementation for `#[unc_bindgen(events(standard = ...))]` attribute.
845///
846/// This derive macro is used to inject the necessary wrapper and logic to auto format
847/// standard event logs. The other appropriate attribute macros are not injected with this macro.
848/// Required attributes below:
849/// ```ignore
850/// #[derive(unc_sdk::serde::Serialize, std::clone::Clone)]
851/// #[serde(crate="unc_sdk::serde")]
852/// #[serde(tag = "event", content = "data")]
853/// #[serde(rename_all="snake_case")]
854/// pub enum MyEvent {
855///     Event
856/// }
857/// ```
858#[proc_macro_derive(EventMetadata, attributes(event_version))]
859pub fn derive_event_attributes(item: TokenStream) -> TokenStream {
860    if let Ok(input) = syn::parse::<ItemEnum>(item) {
861        let name = &input.ident;
862        // get `standard` const injected from `unc_events`
863        let standard_name = format!("{}_event_standard", name);
864        let standard_ident = syn::Ident::new(&standard_name, Span::call_site());
865        // version from each attribute macro
866        let mut event_meta: Vec<proc_macro2::TokenStream> = vec![];
867        for var in &input.variants {
868            if let Some(version) = core_impl::get_event_version(var) {
869                let var_ident = &var.ident;
870                event_meta.push(quote! {
871                    #name::#var_ident { .. } => {(::std::string::ToString::to_string(&#standard_ident), ::std::string::ToString::to_string(#version))}
872                })
873            } else {
874                return TokenStream::from(
875                    syn::Error::new(
876                        Span::call_site(),
877                        "Unc events must have `event_version`. Must have a single string literal value.",
878                    )
879                    .to_compile_error(),
880                );
881            }
882        }
883
884        // handle lifetimes, generics, and where clauses
885        let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl();
886        // add `'unc_event` lifetime for user defined events
887        let mut generics = input.generics.clone();
888        let event_lifetime = syn::Lifetime::new("'unc_event", Span::call_site());
889        generics.params.insert(
890            0,
891            syn::GenericParam::Lifetime(syn::LifetimeParam::new(event_lifetime.clone())),
892        );
893        let (custom_impl_generics, ..) = generics.split_for_impl();
894
895        TokenStream::from(quote! {
896            impl #impl_generics #name #type_generics #where_clause {
897                pub fn emit(&self) {
898                    use ::std::string::String;
899
900                    let (standard, version): (String, String) = match self {
901                        #(#event_meta),*
902                    };
903
904                    #[derive(::unc_sdk::serde::Serialize)]
905                    #[serde(crate="::unc_sdk::serde")]
906                    #[serde(rename_all="snake_case")]
907                    struct EventBuilder #custom_impl_generics #where_clause {
908                        standard: String,
909                        version: String,
910                        #[serde(flatten)]
911                        event_data: &#event_lifetime #name #type_generics
912                    }
913                    let event = EventBuilder { standard, version, event_data: self };
914                    let json = ::unc_sdk::serde_json::to_string(&event)
915                            .unwrap_or_else(|_| ::unc_sdk::env::abort());
916                    ::unc_sdk::env::log_str(&::std::format!("EVENT_JSON:{}", json));
917                }
918            }
919        })
920    } else {
921        TokenStream::from(
922            syn::Error::new(
923                Span::call_site(),
924                "EventMetadata can only be used as a derive on enums.",
925            )
926            .to_compile_error(),
927        )
928    }
929}