solana_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 static program ID
48        pub static ID: #pubkey_type = #id;
49
50        /// Confirms that a given pubkey is equivalent to the program ID
51        pub fn check_id(id: &#pubkey_type) -> bool {
52            id == &ID
53        }
54
55        /// Returns the program ID
56        pub fn id() -> #pubkey_type {
57            ID
58        }
59
60        #[cfg(test)]
61        #[test]
62        fn test_id() {
63            assert!(check_id(&id()));
64        }
65    });
66}
67
68fn deprecated_id_to_tokens(
69    id: &proc_macro2::TokenStream,
70    pubkey_type: proc_macro2::TokenStream,
71    tokens: &mut proc_macro2::TokenStream,
72) {
73    tokens.extend(quote! {
74        /// The static program ID
75        pub static ID: #pubkey_type = #id;
76
77        /// Confirms that a given pubkey is equivalent to the program ID
78        #[deprecated()]
79        pub fn check_id(id: &#pubkey_type) -> bool {
80            id == &ID
81        }
82
83        /// Returns the program ID
84        #[deprecated()]
85        pub fn id() -> #pubkey_type {
86            ID
87        }
88
89        #[cfg(test)]
90        #[test]
91            fn test_id() {
92            #[allow(deprecated)]
93            assert!(check_id(&id()));
94        }
95    });
96}
97
98struct SdkPubkey(proc_macro2::TokenStream);
99
100impl Parse for SdkPubkey {
101    fn parse(input: ParseStream) -> Result<Self> {
102        parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
103    }
104}
105
106impl ToTokens for SdkPubkey {
107    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
108        let id = &self.0;
109        tokens.extend(quote! {#id})
110    }
111}
112
113struct ProgramSdkPubkey(proc_macro2::TokenStream);
114
115impl Parse for ProgramSdkPubkey {
116    fn parse(input: ParseStream) -> Result<Self> {
117        parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
118    }
119}
120
121impl ToTokens for ProgramSdkPubkey {
122    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
123        let id = &self.0;
124        tokens.extend(quote! {#id})
125    }
126}
127
128struct Id(proc_macro2::TokenStream);
129
130impl Parse for Id {
131    fn parse(input: ParseStream) -> Result<Self> {
132        parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
133    }
134}
135
136impl ToTokens for Id {
137    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
138        id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
139    }
140}
141
142struct IdDeprecated(proc_macro2::TokenStream);
143
144impl Parse for IdDeprecated {
145    fn parse(input: ParseStream) -> Result<Self> {
146        parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
147    }
148}
149
150impl ToTokens for IdDeprecated {
151    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
152        deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
153    }
154}
155
156struct ProgramSdkId(proc_macro2::TokenStream);
157impl Parse for ProgramSdkId {
158    fn parse(input: ParseStream) -> Result<Self> {
159        parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
160    }
161}
162
163impl ToTokens for ProgramSdkId {
164    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
165        id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
166    }
167}
168
169struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
170impl Parse for ProgramSdkIdDeprecated {
171    fn parse(input: ParseStream) -> Result<Self> {
172        parse_id(input, quote! { ::solana_program::pubkey::Pubkey }).map(Self)
173    }
174}
175
176impl ToTokens for ProgramSdkIdDeprecated {
177    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
178        deprecated_id_to_tokens(&self.0, quote! { ::solana_program::pubkey::Pubkey }, tokens)
179    }
180}
181
182#[allow(dead_code)] // `respan` may be compiled out
183struct RespanInput {
184    to_respan: Path,
185    respan_using: Span,
186}
187
188impl Parse for RespanInput {
189    fn parse(input: ParseStream) -> Result<Self> {
190        let to_respan: Path = input.parse()?;
191        let _comma: Token![,] = input.parse()?;
192        let respan_tree: TokenTree = input.parse()?;
193        match respan_tree {
194            TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
195                let ident: Ident = syn::parse2(g.stream())?;
196                Ok(RespanInput {
197                    to_respan,
198                    respan_using: ident.span(),
199                })
200            }
201            TokenTree::Ident(i) => Ok(RespanInput {
202                to_respan,
203                respan_using: i.span(),
204            }),
205            val => Err(syn::Error::new_spanned(
206                val,
207                "expected None-delimited group",
208            )),
209        }
210    }
211}
212
213/// A proc-macro which respans the tokens in its first argument (a `Path`)
214/// to be resolved at the tokens of its second argument.
215/// For internal use only.
216///
217/// There must be exactly one comma in the input,
218/// which is used to separate the two arguments.
219/// The second argument should be exactly one token.
220///
221/// For example, `respan!($crate::foo, with_span)`
222/// produces the tokens `$crate::foo`, but resolved
223/// at the span of `with_span`.
224///
225/// The input to this function should be very short -
226/// its only purpose is to override the span of a token
227/// sequence containing `$crate`. For all other purposes,
228/// a more general proc-macro should be used.
229#[rustversion::since(1.46.0)] // `Span::resolved_at` is stable in 1.46.0 and above
230#[proc_macro]
231pub fn respan(input: TokenStream) -> TokenStream {
232    // Obtain the `Path` we are going to respan, and the ident
233    // whose span we will be using.
234    let RespanInput {
235        to_respan,
236        respan_using,
237    } = parse_macro_input!(input as RespanInput);
238    // Respan all of the tokens in the `Path`
239    let to_respan: proc_macro2::TokenStream = to_respan
240        .into_token_stream()
241        .into_iter()
242        .map(|mut t| {
243            // Combine the location of the token with the resolution behavior of `respan_using`
244            let new_span: Span = t.span().resolved_at(respan_using);
245            t.set_span(new_span);
246            t
247        })
248        .collect();
249    TokenStream::from(to_respan)
250}
251
252#[proc_macro]
253pub fn pubkey(input: TokenStream) -> TokenStream {
254    let id = parse_macro_input!(input as SdkPubkey);
255    TokenStream::from(quote! {#id})
256}
257
258#[proc_macro]
259pub fn program_pubkey(input: TokenStream) -> TokenStream {
260    let id = parse_macro_input!(input as ProgramSdkPubkey);
261    TokenStream::from(quote! {#id})
262}
263
264#[proc_macro]
265pub fn declare_id(input: TokenStream) -> TokenStream {
266    let id = parse_macro_input!(input as Id);
267    TokenStream::from(quote! {#id})
268}
269
270#[proc_macro]
271pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
272    let id = parse_macro_input!(input as IdDeprecated);
273    TokenStream::from(quote! {#id})
274}
275
276#[proc_macro]
277pub fn program_declare_id(input: TokenStream) -> TokenStream {
278    let id = parse_macro_input!(input as ProgramSdkId);
279    TokenStream::from(quote! {#id})
280}
281
282#[proc_macro]
283pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
284    let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
285    TokenStream::from(quote! {#id})
286}
287
288fn parse_pubkey(
289    id_literal: &LitStr,
290    pubkey_type: &proc_macro2::TokenStream,
291) -> Result<proc_macro2::TokenStream> {
292    let id_vec = bs58::decode(id_literal.value())
293        .into_vec()
294        .map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
295    let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
296        syn::Error::new_spanned(
297            id_literal,
298            format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
299        )
300    })?;
301    let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
302    Ok(quote! {
303        #pubkey_type::new_from_array(
304            [#(#bytes,)*]
305        )
306    })
307}
308
309struct Pubkeys {
310    method: Ident,
311    num: usize,
312    pubkeys: proc_macro2::TokenStream,
313}
314impl Parse for Pubkeys {
315    fn parse(input: ParseStream) -> Result<Self> {
316        let pubkey_type = quote! {
317            ::solana_sdk::pubkey::Pubkey
318        };
319
320        let method = input.parse()?;
321        let _comma: Token![,] = input.parse()?;
322        let (num, pubkeys) = if input.peek(syn::LitStr) {
323            let id_literal: LitStr = input.parse()?;
324            (1, parse_pubkey(&id_literal, &pubkey_type)?)
325        } else if input.peek(Bracket) {
326            let pubkey_strings;
327            bracketed!(pubkey_strings in input);
328            let punctuated: Punctuated<LitStr, Token![,]> =
329                Punctuated::parse_terminated(&pubkey_strings)?;
330            let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
331            for string in punctuated.iter() {
332                pubkeys.push(parse_pubkey(string, &pubkey_type)?);
333            }
334            (pubkeys.len(), quote! {#pubkeys})
335        } else {
336            let stream: proc_macro2::TokenStream = input.parse()?;
337            return Err(syn::Error::new_spanned(stream, "unexpected token"));
338        };
339
340        Ok(Pubkeys {
341            method,
342            num,
343            pubkeys,
344        })
345    }
346}
347
348impl ToTokens for Pubkeys {
349    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
350        let Pubkeys {
351            method,
352            num,
353            pubkeys,
354        } = self;
355
356        let pubkey_type = quote! {
357            ::solana_sdk::pubkey::Pubkey
358        };
359        if *num == 1 {
360            tokens.extend(quote! {
361                pub fn #method() -> #pubkey_type {
362                    #pubkeys
363                }
364            });
365        } else {
366            tokens.extend(quote! {
367                pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
368                    vec![#pubkeys]
369                }
370            });
371        }
372    }
373}
374
375#[proc_macro]
376pub fn pubkeys(input: TokenStream) -> TokenStream {
377    let pubkeys = parse_macro_input!(input as Pubkeys);
378    TokenStream::from(quote! {#pubkeys})
379}
380
381// The normal `wasm_bindgen` macro generates a .bss section which causes the resulting
382// BPF program to fail to load, so for now this stub should be used when building for BPF
383#[proc_macro_attribute]
384pub fn wasm_bindgen_stub(_attr: TokenStream, item: TokenStream) -> TokenStream {
385    match parse_macro_input!(item as syn::Item) {
386        syn::Item::Struct(mut item_struct) => {
387            if let syn::Fields::Named(fields) = &mut item_struct.fields {
388                // Strip out any `#[wasm_bindgen]` added to struct fields. This is custom
389                // syntax supplied by the normal `wasm_bindgen` macro.
390                for field in fields.named.iter_mut() {
391                    field.attrs.retain(|attr| {
392                        !attr
393                            .path
394                            .segments
395                            .iter()
396                            .any(|segment| segment.ident == "wasm_bindgen")
397                    });
398                }
399            }
400            quote! { #item_struct }
401        }
402        item => {
403            quote!(#item)
404        }
405    }
406    .into()
407}