gemachain_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 proc_macro::TokenStream;
8use proc_macro2::{Delimiter, Span, TokenTree};
9use quote::{quote, ToTokens};
10use std::convert::TryFrom;
11use syn::{
12    bracketed,
13    parse::{Parse, ParseStream, Result},
14    parse_macro_input,
15    punctuated::Punctuated,
16    token::Bracket,
17    Expr, Ident, LitByte, LitStr, Path, Token,
18};
19
20fn parse_id(
21    input: ParseStream,
22    pubkey_type: proc_macro2::TokenStream,
23) -> Result<proc_macro2::TokenStream> {
24    let id = if input.peek(syn::LitStr) {
25        let id_literal: LitStr = input.parse()?;
26        parse_pubkey(&id_literal, &pubkey_type)?
27    } else {
28        let expr: Expr = input.parse()?;
29        quote! { #expr }
30    };
31
32    if !input.is_empty() {
33        let stream: proc_macro2::TokenStream = input.parse()?;
34        return Err(syn::Error::new_spanned(stream, "unexpected token"));
35    }
36    Ok(id)
37}
38
39fn id_to_tokens(
40    id: &proc_macro2::TokenStream,
41    pubkey_type: proc_macro2::TokenStream,
42    tokens: &mut proc_macro2::TokenStream,
43) {
44    tokens.extend(quote! {
45        /// The static program ID
46        pub static ID: #pubkey_type = #id;
47
48        /// Confirms that a given pubkey is equivalent to the program ID
49        pub fn check_id(id: &#pubkey_type) -> bool {
50            id == &ID
51        }
52
53        /// Returns the program ID
54        pub fn id() -> #pubkey_type {
55            ID
56        }
57
58        #[cfg(test)]
59        #[test]
60        fn test_id() {
61            assert!(check_id(&id()));
62        }
63    });
64}
65
66fn deprecated_id_to_tokens(
67    id: &proc_macro2::TokenStream,
68    pubkey_type: proc_macro2::TokenStream,
69    tokens: &mut proc_macro2::TokenStream,
70) {
71    tokens.extend(quote! {
72        /// The static program ID
73        pub static ID: #pubkey_type = #id;
74
75        /// Confirms that a given pubkey is equivalent to the program ID
76        #[deprecated()]
77        pub fn check_id(id: &#pubkey_type) -> bool {
78            id == &ID
79        }
80
81        /// Returns the program ID
82        #[deprecated()]
83        pub fn id() -> #pubkey_type {
84            ID
85        }
86
87        #[cfg(test)]
88        #[test]
89            fn test_id() {
90            #[allow(deprecated)]
91            assert!(check_id(&id()));
92        }
93    });
94}
95
96struct Id(proc_macro2::TokenStream);
97
98impl Parse for Id {
99    fn parse(input: ParseStream) -> Result<Self> {
100        parse_id(input, quote! { ::gemachain_sdk::pubkey::Pubkey }).map(Self)
101    }
102}
103
104impl ToTokens for Id {
105    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
106        id_to_tokens(&self.0, quote! { ::gemachain_sdk::pubkey::Pubkey }, tokens)
107    }
108}
109
110struct IdDeprecated(proc_macro2::TokenStream);
111
112impl Parse for IdDeprecated {
113    fn parse(input: ParseStream) -> Result<Self> {
114        parse_id(input, quote! { ::gemachain_sdk::pubkey::Pubkey }).map(Self)
115    }
116}
117
118impl ToTokens for IdDeprecated {
119    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
120        deprecated_id_to_tokens(&self.0, quote! { ::gemachain_sdk::pubkey::Pubkey }, tokens)
121    }
122}
123
124struct ProgramSdkId(proc_macro2::TokenStream);
125impl Parse for ProgramSdkId {
126    fn parse(input: ParseStream) -> Result<Self> {
127        parse_id(input, quote! { ::gemachain_program::pubkey::Pubkey }).map(Self)
128    }
129}
130
131impl ToTokens for ProgramSdkId {
132    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
133        id_to_tokens(&self.0, quote! { ::gemachain_program::pubkey::Pubkey }, tokens)
134    }
135}
136
137struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
138impl Parse for ProgramSdkIdDeprecated {
139    fn parse(input: ParseStream) -> Result<Self> {
140        parse_id(input, quote! { ::gemachain_program::pubkey::Pubkey }).map(Self)
141    }
142}
143
144impl ToTokens for ProgramSdkIdDeprecated {
145    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
146        deprecated_id_to_tokens(&self.0, quote! { ::gemachain_program::pubkey::Pubkey }, tokens)
147    }
148}
149
150#[allow(dead_code)] // `respan` may be compiled out
151struct RespanInput {
152    to_respan: Path,
153    respan_using: Span,
154}
155
156impl Parse for RespanInput {
157    fn parse(input: ParseStream) -> Result<Self> {
158        let to_respan: Path = input.parse()?;
159        let _comma: Token![,] = input.parse()?;
160        let respan_tree: TokenTree = input.parse()?;
161        match respan_tree {
162            TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
163                let ident: Ident = syn::parse2(g.stream())?;
164                Ok(RespanInput {
165                    to_respan,
166                    respan_using: ident.span(),
167                })
168            }
169            val => Err(syn::Error::new_spanned(
170                val,
171                "expected None-delimited group",
172            )),
173        }
174    }
175}
176
177/// A proc-macro which respans the tokens in its first argument (a `Path`)
178/// to be resolved at the tokens of its second argument.
179/// For internal use only.
180///
181/// There must be exactly one comma in the input,
182/// which is used to separate the two arguments.
183/// The second argument should be exactly one token.
184///
185/// For example, `respan!($crate::foo, with_span)`
186/// produces the tokens `$crate::foo`, but resolved
187/// at the span of `with_span`.
188///
189/// The input to this function should be very short -
190/// its only purpose is to override the span of a token
191/// sequence containing `$crate`. For all other purposes,
192/// a more general proc-macro should be used.
193#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
194#[proc_macro]
195pub fn respan(input: TokenStream) -> TokenStream {
196    // Obtain the `Path` we are going to respan, and the ident
197    // whose span we will be using.
198    let RespanInput {
199        to_respan,
200        respan_using,
201    } = parse_macro_input!(input as RespanInput);
202    // Respan all of the tokens in the `Path`
203    let to_respan: proc_macro2::TokenStream = to_respan
204        .into_token_stream()
205        .into_iter()
206        .map(|mut t| {
207            // Combine the location of the token with the resolution behavior of `respan_using`
208            let new_span: Span = t.span().resolved_at(respan_using);
209            t.set_span(new_span);
210            t
211        })
212        .collect();
213    TokenStream::from(to_respan)
214}
215
216#[proc_macro]
217pub fn declare_id(input: TokenStream) -> TokenStream {
218    let id = parse_macro_input!(input as Id);
219    TokenStream::from(quote! {#id})
220}
221
222#[proc_macro]
223pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
224    let id = parse_macro_input!(input as IdDeprecated);
225    TokenStream::from(quote! {#id})
226}
227
228#[proc_macro]
229pub fn program_declare_id(input: TokenStream) -> TokenStream {
230    let id = parse_macro_input!(input as ProgramSdkId);
231    TokenStream::from(quote! {#id})
232}
233
234#[proc_macro]
235pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
236    let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
237    TokenStream::from(quote! {#id})
238}
239
240fn parse_pubkey(
241    id_literal: &LitStr,
242    pubkey_type: &proc_macro2::TokenStream,
243) -> Result<proc_macro2::TokenStream> {
244    let id_vec = bs58::decode(id_literal.value())
245        .into_vec()
246        .map_err(|_| syn::Error::new_spanned(&id_literal, "failed to decode base58 string"))?;
247    let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
248        syn::Error::new_spanned(
249            &id_literal,
250            format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
251        )
252    })?;
253    let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
254    Ok(quote! {
255        #pubkey_type::new_from_array(
256            [#(#bytes,)*]
257        )
258    })
259}
260
261struct Pubkeys {
262    method: Ident,
263    num: usize,
264    pubkeys: proc_macro2::TokenStream,
265}
266impl Parse for Pubkeys {
267    fn parse(input: ParseStream) -> Result<Self> {
268        let pubkey_type = quote! {
269            ::gemachain_sdk::pubkey::Pubkey
270        };
271
272        let method = input.parse()?;
273        let _comma: Token![,] = input.parse()?;
274        let (num, pubkeys) = if input.peek(syn::LitStr) {
275            let id_literal: LitStr = input.parse()?;
276            (1, parse_pubkey(&id_literal, &pubkey_type)?)
277        } else if input.peek(Bracket) {
278            let pubkey_strings;
279            bracketed!(pubkey_strings in input);
280            let punctuated: Punctuated<LitStr, Token![,]> =
281                Punctuated::parse_terminated(&pubkey_strings)?;
282            let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
283            for string in punctuated.iter() {
284                pubkeys.push(parse_pubkey(string, &pubkey_type)?);
285            }
286            (pubkeys.len(), quote! {#pubkeys})
287        } else {
288            let stream: proc_macro2::TokenStream = input.parse()?;
289            return Err(syn::Error::new_spanned(stream, "unexpected token"));
290        };
291
292        Ok(Pubkeys {
293            method,
294            num,
295            pubkeys,
296        })
297    }
298}
299
300impl ToTokens for Pubkeys {
301    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
302        let Pubkeys {
303            method,
304            num,
305            pubkeys,
306        } = self;
307
308        let pubkey_type = quote! {
309            ::gemachain_sdk::pubkey::Pubkey
310        };
311        if *num == 1 {
312            tokens.extend(quote! {
313                pub fn #method() -> #pubkey_type {
314                    #pubkeys
315                }
316            });
317        } else {
318            tokens.extend(quote! {
319                pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
320                    vec![#pubkeys]
321                }
322            });
323        }
324    }
325}
326
327#[proc_macro]
328pub fn pubkeys(input: TokenStream) -> TokenStream {
329    let pubkeys = parse_macro_input!(input as Pubkeys);
330    TokenStream::from(quote! {#pubkeys})
331}