miraland_sdk_macro/
lib.rs

1//! Convenience macro to declare a static public key and functions to interact with it
2//!
3//! Input: a single literal base58 string representation of a program's id
4
5extern crate proc_macro;
6
7use {
8    proc_macro::TokenStream,
9    proc_macro2::{Delimiter, Span, TokenTree},
10    quote::{quote, ToTokens},
11    std::convert::TryFrom,
12    syn::{
13        bracketed,
14        parse::{Parse, ParseStream, Result},
15        parse_macro_input,
16        punctuated::Punctuated,
17        token::Bracket,
18        Expr, Ident, LitByte, LitStr, Path, Token,
19    },
20};
21
22fn parse_id(
23    input: ParseStream,
24    pubkey_type: proc_macro2::TokenStream,
25) -> Result<proc_macro2::TokenStream> {
26    let id = if input.peek(syn::LitStr) {
27        let id_literal: LitStr = input.parse()?;
28        parse_pubkey(&id_literal, &pubkey_type)?
29    } else {
30        let expr: Expr = input.parse()?;
31        quote! { #expr }
32    };
33
34    if !input.is_empty() {
35        let stream: proc_macro2::TokenStream = input.parse()?;
36        return Err(syn::Error::new_spanned(stream, "unexpected token"));
37    }
38    Ok(id)
39}
40
41fn id_to_tokens(
42    id: &proc_macro2::TokenStream,
43    pubkey_type: proc_macro2::TokenStream,
44    tokens: &mut proc_macro2::TokenStream,
45) {
46    tokens.extend(quote! {
47        /// The const program ID.
48        pub const ID: #pubkey_type = #id;
49
50        /// Returns `true` if given pubkey is the program ID.
51        // TODO make this const once `derive_const` makes it out of nightly
52        // and we can `derive_const(PartialEq)` on `Pubkey`.
53        pub fn check_id(id: &#pubkey_type) -> bool {
54            id == &ID
55        }
56
57        /// Returns the program ID.
58        pub const fn id() -> #pubkey_type {
59            ID
60        }
61
62        #[cfg(test)]
63        #[test]
64        fn test_id() {
65            assert!(check_id(&id()));
66        }
67    });
68}
69
70fn deprecated_id_to_tokens(
71    id: &proc_macro2::TokenStream,
72    pubkey_type: proc_macro2::TokenStream,
73    tokens: &mut proc_macro2::TokenStream,
74) {
75    tokens.extend(quote! {
76        /// The static program ID.
77        pub static ID: #pubkey_type = #id;
78
79        /// Returns `true` if given pubkey is the program ID.
80        #[deprecated()]
81        pub fn check_id(id: &#pubkey_type) -> bool {
82            id == &ID
83        }
84
85        /// Returns the program ID.
86        #[deprecated()]
87        pub fn id() -> #pubkey_type {
88            ID
89        }
90
91        #[cfg(test)]
92        #[test]
93        #[allow(deprecated)]
94        fn test_id() {
95            assert!(check_id(&id()));
96        }
97    });
98}
99
100struct SdkPubkey(proc_macro2::TokenStream);
101
102impl Parse for SdkPubkey {
103    fn parse(input: ParseStream) -> Result<Self> {
104        parse_id(input, quote! { ::miraland_sdk::pubkey::Pubkey }).map(Self)
105    }
106}
107
108impl ToTokens for SdkPubkey {
109    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
110        let id = &self.0;
111        tokens.extend(quote! {#id})
112    }
113}
114
115struct ProgramSdkPubkey(proc_macro2::TokenStream);
116
117impl Parse for ProgramSdkPubkey {
118    fn parse(input: ParseStream) -> Result<Self> {
119        parse_id(input, quote! { ::miraland_program::pubkey::Pubkey }).map(Self)
120    }
121}
122
123impl ToTokens for ProgramSdkPubkey {
124    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
125        let id = &self.0;
126        tokens.extend(quote! {#id})
127    }
128}
129
130struct Id(proc_macro2::TokenStream);
131
132impl Parse for Id {
133    fn parse(input: ParseStream) -> Result<Self> {
134        parse_id(input, quote! { ::miraland_sdk::pubkey::Pubkey }).map(Self)
135    }
136}
137
138impl ToTokens for Id {
139    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
140        id_to_tokens(&self.0, quote! { ::miraland_sdk::pubkey::Pubkey }, tokens)
141    }
142}
143
144struct IdDeprecated(proc_macro2::TokenStream);
145
146impl Parse for IdDeprecated {
147    fn parse(input: ParseStream) -> Result<Self> {
148        parse_id(input, quote! { ::miraland_sdk::pubkey::Pubkey }).map(Self)
149    }
150}
151
152impl ToTokens for IdDeprecated {
153    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
154        deprecated_id_to_tokens(&self.0, quote! { ::miraland_sdk::pubkey::Pubkey }, tokens)
155    }
156}
157
158struct ProgramSdkId(proc_macro2::TokenStream);
159impl Parse for ProgramSdkId {
160    fn parse(input: ParseStream) -> Result<Self> {
161        parse_id(input, quote! { ::miraland_program::pubkey::Pubkey }).map(Self)
162    }
163}
164
165impl ToTokens for ProgramSdkId {
166    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
167        id_to_tokens(&self.0, quote! { ::miraland_program::pubkey::Pubkey }, tokens)
168    }
169}
170
171struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
172impl Parse for ProgramSdkIdDeprecated {
173    fn parse(input: ParseStream) -> Result<Self> {
174        parse_id(input, quote! { ::miraland_program::pubkey::Pubkey }).map(Self)
175    }
176}
177
178impl ToTokens for ProgramSdkIdDeprecated {
179    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
180        deprecated_id_to_tokens(&self.0, quote! { ::miraland_program::pubkey::Pubkey }, tokens)
181    }
182}
183
184#[allow(dead_code)] // `respan` may be compiled out
185struct RespanInput {
186    to_respan: Path,
187    respan_using: Span,
188}
189
190impl Parse for RespanInput {
191    fn parse(input: ParseStream) -> Result<Self> {
192        let to_respan: Path = input.parse()?;
193        let _comma: Token![,] = input.parse()?;
194        let respan_tree: TokenTree = input.parse()?;
195        match respan_tree {
196            TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
197                let ident: Ident = syn::parse2(g.stream())?;
198                Ok(RespanInput {
199                    to_respan,
200                    respan_using: ident.span(),
201                })
202            }
203            TokenTree::Ident(i) => Ok(RespanInput {
204                to_respan,
205                respan_using: i.span(),
206            }),
207            val => Err(syn::Error::new_spanned(
208                val,
209                "expected None-delimited group",
210            )),
211        }
212    }
213}
214
215/// A proc-macro which respans the tokens in its first argument (a `Path`)
216/// to be resolved at the tokens of its second argument.
217/// For internal use only.
218///
219/// There must be exactly one comma in the input,
220/// which is used to separate the two arguments.
221/// The second argument should be exactly one token.
222///
223/// For example, `respan!($crate::foo, with_span)`
224/// produces the tokens `$crate::foo`, but resolved
225/// at the span of `with_span`.
226///
227/// The input to this function should be very short -
228/// its only purpose is to override the span of a token
229/// sequence containing `$crate`. For all other purposes,
230/// a more general proc-macro should be used.
231#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
232#[proc_macro]
233pub fn respan(input: TokenStream) -> TokenStream {
234    // Obtain the `Path` we are going to respan, and the ident
235    // whose span we will be using.
236    let RespanInput {
237        to_respan,
238        respan_using,
239    } = parse_macro_input!(input as RespanInput);
240    // Respan all of the tokens in the `Path`
241    let to_respan: proc_macro2::TokenStream = to_respan
242        .into_token_stream()
243        .into_iter()
244        .map(|mut t| {
245            // Combine the location of the token with the resolution behavior of `respan_using`
246            let new_span: Span = t.span().resolved_at(respan_using);
247            t.set_span(new_span);
248            t
249        })
250        .collect();
251    TokenStream::from(to_respan)
252}
253
254#[proc_macro]
255pub fn pubkey(input: TokenStream) -> TokenStream {
256    let id = parse_macro_input!(input as SdkPubkey);
257    TokenStream::from(quote! {#id})
258}
259
260#[proc_macro]
261pub fn program_pubkey(input: TokenStream) -> TokenStream {
262    let id = parse_macro_input!(input as ProgramSdkPubkey);
263    TokenStream::from(quote! {#id})
264}
265
266#[proc_macro]
267pub fn declare_id(input: TokenStream) -> TokenStream {
268    let id = parse_macro_input!(input as Id);
269    TokenStream::from(quote! {#id})
270}
271
272#[proc_macro]
273pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
274    let id = parse_macro_input!(input as IdDeprecated);
275    TokenStream::from(quote! {#id})
276}
277
278#[proc_macro]
279pub fn program_declare_id(input: TokenStream) -> TokenStream {
280    let id = parse_macro_input!(input as ProgramSdkId);
281    TokenStream::from(quote! {#id})
282}
283
284#[proc_macro]
285pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
286    let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
287    TokenStream::from(quote! {#id})
288}
289
290fn parse_pubkey(
291    id_literal: &LitStr,
292    pubkey_type: &proc_macro2::TokenStream,
293) -> Result<proc_macro2::TokenStream> {
294    let id_vec = bs58::decode(id_literal.value())
295        .into_vec()
296        .map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
297    let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
298        syn::Error::new_spanned(
299            id_literal,
300            format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
301        )
302    })?;
303    let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
304    Ok(quote! {
305        #pubkey_type::new_from_array(
306            [#(#bytes,)*]
307        )
308    })
309}
310
311struct Pubkeys {
312    method: Ident,
313    num: usize,
314    pubkeys: proc_macro2::TokenStream,
315}
316impl Parse for Pubkeys {
317    fn parse(input: ParseStream) -> Result<Self> {
318        let pubkey_type = quote! {
319            ::miraland_sdk::pubkey::Pubkey
320        };
321
322        let method = input.parse()?;
323        let _comma: Token![,] = input.parse()?;
324        let (num, pubkeys) = if input.peek(syn::LitStr) {
325            let id_literal: LitStr = input.parse()?;
326            (1, parse_pubkey(&id_literal, &pubkey_type)?)
327        } else if input.peek(Bracket) {
328            let pubkey_strings;
329            bracketed!(pubkey_strings in input);
330            let punctuated: Punctuated<LitStr, Token![,]> =
331                Punctuated::parse_terminated(&pubkey_strings)?;
332            let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
333            for string in punctuated.iter() {
334                pubkeys.push(parse_pubkey(string, &pubkey_type)?);
335            }
336            (pubkeys.len(), quote! {#pubkeys})
337        } else {
338            let stream: proc_macro2::TokenStream = input.parse()?;
339            return Err(syn::Error::new_spanned(stream, "unexpected token"));
340        };
341
342        Ok(Pubkeys {
343            method,
344            num,
345            pubkeys,
346        })
347    }
348}
349
350impl ToTokens for Pubkeys {
351    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
352        let Pubkeys {
353            method,
354            num,
355            pubkeys,
356        } = self;
357
358        let pubkey_type = quote! {
359            ::miraland_sdk::pubkey::Pubkey
360        };
361        if *num == 1 {
362            tokens.extend(quote! {
363                pub fn #method() -> #pubkey_type {
364                    #pubkeys
365                }
366            });
367        } else {
368            tokens.extend(quote! {
369                pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
370                    vec![#pubkeys]
371                }
372            });
373        }
374    }
375}
376
377#[proc_macro]
378pub fn pubkeys(input: TokenStream) -> TokenStream {
379    let pubkeys = parse_macro_input!(input as Pubkeys);
380    TokenStream::from(quote! {#pubkeys})
381}
382
383// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
384// SBF program to fail to load, so for now this stub should be used when building for SBF
385#[proc_macro_attribute]
386pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
387    match parse_macro_input!(item as syn::Item) {
388        syn::Item::Struct(mut item_struct) => {
389            if let syn::Fields::Named(fields) = &mut item_struct.fields {
390                // Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
391                // syntax supplied by the normal `wasm_bindgen` macro.
392                for field in fields.named.iter_mut() {
393                    field.attrs.retain(|attr| {
394                        !attr
395                            .path()
396                            .segments
397                            .iter()
398                            .any(|segment| segment.ident == "wasm_bindgen")
399                    });
400                }
401            }
402            quote! { #item_struct }
403        }
404        item => {
405            quote!(#item)
406        }
407    }
408    .into()
409}
410
411// Sets padding in structures to zero explicitly.
412// Otherwise padding could be inconsistent across the network and lead to divergence / consensus failures.
413#[proc_macro_derive(CloneZeroed)]
414pub fn derive_clone_zeroed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
415    match parse_macro_input!(input as syn::Item) {
416        syn::Item::Struct(item_struct) => {
417            let clone_statements = match item_struct.fields {
418                syn::Fields::Named(ref fields) => fields.named.iter().map(|f| {
419                    let name = &f.ident;
420                    quote! {
421                        std::ptr::addr_of_mut!((*ptr).#name).write(self.#name);
422                    }
423                }),
424                _ => unimplemented!(),
425            };
426            let name = &item_struct.ident;
427            quote! {
428                impl Clone for #name {
429                    // Clippy lint `incorrect_clone_impl_on_copy_type` requires that clone
430                    // implementations on `Copy` types are simply wrappers of `Copy`.
431                    // This is not the case here, and intentionally so because we want to
432                    // guarantee zeroed padding.
433                    fn clone(&self) -> Self {
434                        let mut value = std::mem::MaybeUninit::<Self>::uninit();
435                        unsafe {
436                            std::ptr::write_bytes(&mut value, 0, 1);
437                            let ptr = value.as_mut_ptr();
438                            #(#clone_statements)*
439                            value.assume_init()
440                        }
441                    }
442                }
443            }
444        }
445        _ => unimplemented!(),
446    }
447    .into()
448}