clone_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::Span,
10    quote::{quote, ToTokens},
11    syn::{
12        bracketed,
13        parse::{Parse, ParseStream, Result},
14        parse_macro_input,
15        punctuated::Punctuated,
16        token::Bracket,
17        Expr, Ident, LitByte, LitStr, Token,
18    },
19};
20
21fn parse_id(
22    input: ParseStream,
23    pubkey_type: proc_macro2::TokenStream,
24) -> Result<proc_macro2::TokenStream> {
25    let id = if input.peek(syn::LitStr) {
26        let id_literal: LitStr = input.parse()?;
27        parse_pubkey(&id_literal, &pubkey_type)?
28    } else {
29        let expr: Expr = input.parse()?;
30        quote! { #expr }
31    };
32
33    if !input.is_empty() {
34        let stream: proc_macro2::TokenStream = input.parse()?;
35        return Err(syn::Error::new_spanned(stream, "unexpected token"));
36    }
37    Ok(id)
38}
39
40fn id_to_tokens(
41    id: &proc_macro2::TokenStream,
42    pubkey_type: proc_macro2::TokenStream,
43    tokens: &mut proc_macro2::TokenStream,
44) {
45    tokens.extend(quote! {
46        /// The const program ID.
47        pub const ID: #pubkey_type = #id;
48
49        /// Returns `true` if given pubkey is the program ID.
50        // TODO make this const once `derive_const` makes it out of nightly
51        // and we can `derive_const(PartialEq)` on `Pubkey`.
52        pub fn check_id(id: &#pubkey_type) -> bool {
53            id == &ID
54        }
55
56        /// Returns the program ID.
57        pub const fn id() -> #pubkey_type {
58            ID
59        }
60
61        #[cfg(test)]
62        #[test]
63        fn test_id() {
64            assert!(check_id(&id()));
65        }
66    });
67}
68
69fn deprecated_id_to_tokens(
70    id: &proc_macro2::TokenStream,
71    pubkey_type: proc_macro2::TokenStream,
72    tokens: &mut proc_macro2::TokenStream,
73) {
74    tokens.extend(quote! {
75        /// The static program ID.
76        pub static ID: #pubkey_type = #id;
77
78        /// Returns `true` if given pubkey is the program ID.
79        #[deprecated()]
80        pub fn check_id(id: &#pubkey_type) -> bool {
81            id == &ID
82        }
83
84        /// Returns the program ID.
85        #[deprecated()]
86        pub fn id() -> #pubkey_type {
87            ID
88        }
89
90        #[cfg(test)]
91        #[test]
92        #[allow(deprecated)]
93        fn test_id() {
94            assert!(check_id(&id()));
95        }
96    });
97}
98
99struct SdkPubkey(proc_macro2::TokenStream);
100
101impl Parse for SdkPubkey {
102    fn parse(input: ParseStream) -> Result<Self> {
103        parse_id(input, quote! { ::clone_solana_sdk::pubkey::Pubkey }).map(Self)
104    }
105}
106
107impl ToTokens for SdkPubkey {
108    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
109        let id = &self.0;
110        tokens.extend(quote! {#id})
111    }
112}
113
114struct ProgramSdkPubkey(proc_macro2::TokenStream);
115
116impl Parse for ProgramSdkPubkey {
117    fn parse(input: ParseStream) -> Result<Self> {
118        parse_id(input, quote! { ::clone_solana_program::pubkey::Pubkey }).map(Self)
119    }
120}
121
122impl ToTokens for ProgramSdkPubkey {
123    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
124        let id = &self.0;
125        tokens.extend(quote! {#id})
126    }
127}
128
129struct Id(proc_macro2::TokenStream);
130
131impl Parse for Id {
132    fn parse(input: ParseStream) -> Result<Self> {
133        parse_id(input, quote! { ::clone_solana_sdk::pubkey::Pubkey }).map(Self)
134    }
135}
136
137impl ToTokens for Id {
138    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
139        id_to_tokens(
140            &self.0,
141            quote! { ::clone_solana_sdk::pubkey::Pubkey },
142            tokens,
143        )
144    }
145}
146
147struct IdDeprecated(proc_macro2::TokenStream);
148
149impl Parse for IdDeprecated {
150    fn parse(input: ParseStream) -> Result<Self> {
151        parse_id(input, quote! { ::clone_solana_sdk::pubkey::Pubkey }).map(Self)
152    }
153}
154
155impl ToTokens for IdDeprecated {
156    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
157        deprecated_id_to_tokens(
158            &self.0,
159            quote! { ::clone_solana_sdk::pubkey::Pubkey },
160            tokens,
161        )
162    }
163}
164
165struct ProgramSdkId(proc_macro2::TokenStream);
166impl Parse for ProgramSdkId {
167    fn parse(input: ParseStream) -> Result<Self> {
168        parse_id(input, quote! { ::clone_solana_program::pubkey::Pubkey }).map(Self)
169    }
170}
171
172impl ToTokens for ProgramSdkId {
173    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
174        id_to_tokens(
175            &self.0,
176            quote! { ::clone_solana_program::pubkey::Pubkey },
177            tokens,
178        )
179    }
180}
181
182struct ProgramSdkIdDeprecated(proc_macro2::TokenStream);
183impl Parse for ProgramSdkIdDeprecated {
184    fn parse(input: ParseStream) -> Result<Self> {
185        parse_id(input, quote! { ::clone_solana_program::pubkey::Pubkey }).map(Self)
186    }
187}
188
189impl ToTokens for ProgramSdkIdDeprecated {
190    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
191        deprecated_id_to_tokens(
192            &self.0,
193            quote! { ::clone_solana_program::pubkey::Pubkey },
194            tokens,
195        )
196    }
197}
198
199#[deprecated(since = "2.1.0", note = "Use `clone_solana_pubkey::pubkey` instead")]
200#[proc_macro]
201pub fn pubkey(input: TokenStream) -> TokenStream {
202    let id = parse_macro_input!(input as SdkPubkey);
203    TokenStream::from(quote! {#id})
204}
205
206#[deprecated(since = "2.1.0", note = "Use `clone_solana_pubkey::pubkey!` instead")]
207#[proc_macro]
208pub fn program_pubkey(input: TokenStream) -> TokenStream {
209    let id = parse_macro_input!(input as ProgramSdkPubkey);
210    TokenStream::from(quote! {#id})
211}
212
213#[proc_macro]
214pub fn declare_id(input: TokenStream) -> TokenStream {
215    let id = parse_macro_input!(input as Id);
216    TokenStream::from(quote! {#id})
217}
218
219#[proc_macro]
220pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
221    let id = parse_macro_input!(input as IdDeprecated);
222    TokenStream::from(quote! {#id})
223}
224
225#[deprecated(
226    since = "2.1.0",
227    note = "Use `clone_solana_pubkey::declare_id` instead"
228)]
229#[proc_macro]
230pub fn program_declare_id(input: TokenStream) -> TokenStream {
231    let id = parse_macro_input!(input as ProgramSdkId);
232    TokenStream::from(quote! {#id})
233}
234
235#[deprecated(
236    since = "2.1.0",
237    note = "Use `clone_solana_pubkey::declare_deprecated_id` instead"
238)]
239#[proc_macro]
240pub fn program_declare_deprecated_id(input: TokenStream) -> TokenStream {
241    let id = parse_macro_input!(input as ProgramSdkIdDeprecated);
242    TokenStream::from(quote! {#id})
243}
244
245fn parse_pubkey(
246    id_literal: &LitStr,
247    pubkey_type: &proc_macro2::TokenStream,
248) -> Result<proc_macro2::TokenStream> {
249    let id_vec = bs58::decode(id_literal.value())
250        .into_vec()
251        .map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
252    let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
253        syn::Error::new_spanned(
254            id_literal,
255            format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
256        )
257    })?;
258    let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
259    Ok(quote! {
260        #pubkey_type::new_from_array(
261            [#(#bytes,)*]
262        )
263    })
264}
265
266struct Pubkeys {
267    method: Ident,
268    num: usize,
269    pubkeys: proc_macro2::TokenStream,
270}
271impl Parse for Pubkeys {
272    fn parse(input: ParseStream) -> Result<Self> {
273        let pubkey_type = quote! {
274            ::clone_solana_sdk::pubkey::Pubkey
275        };
276
277        let method = input.parse()?;
278        let _comma: Token![,] = input.parse()?;
279        let (num, pubkeys) = if input.peek(syn::LitStr) {
280            let id_literal: LitStr = input.parse()?;
281            (1, parse_pubkey(&id_literal, &pubkey_type)?)
282        } else if input.peek(Bracket) {
283            let pubkey_strings;
284            bracketed!(pubkey_strings in input);
285            let punctuated: Punctuated<LitStr, Token![,]> =
286                Punctuated::parse_terminated(&pubkey_strings)?;
287            let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
288            for string in punctuated.iter() {
289                pubkeys.push(parse_pubkey(string, &pubkey_type)?);
290            }
291            (pubkeys.len(), quote! {#pubkeys})
292        } else {
293            let stream: proc_macro2::TokenStream = input.parse()?;
294            return Err(syn::Error::new_spanned(stream, "unexpected token"));
295        };
296
297        Ok(Pubkeys {
298            method,
299            num,
300            pubkeys,
301        })
302    }
303}
304
305impl ToTokens for Pubkeys {
306    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
307        let Pubkeys {
308            method,
309            num,
310            pubkeys,
311        } = self;
312
313        let pubkey_type = quote! {
314            ::clone_solana_sdk::pubkey::Pubkey
315        };
316        if *num == 1 {
317            tokens.extend(quote! {
318                pub fn #method() -> #pubkey_type {
319                    #pubkeys
320                }
321            });
322        } else {
323            tokens.extend(quote! {
324                pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
325                    vec![#pubkeys]
326                }
327            });
328        }
329    }
330}
331
332#[proc_macro]
333pub fn pubkeys(input: TokenStream) -> TokenStream {
334    let pubkeys = parse_macro_input!(input as Pubkeys);
335    TokenStream::from(quote! {#pubkeys})
336}
337
338// Sets padding in structures to zero explicitly.
339// Otherwise padding could be inconsistent across the network and lead to divergence / consensus failures.
340#[proc_macro_derive(CloneZeroed)]
341pub fn derive_clone_zeroed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
342    match parse_macro_input!(input as syn::Item) {
343        syn::Item::Struct(item_struct) => {
344            let clone_statements = match item_struct.fields {
345                syn::Fields::Named(ref fields) => fields.named.iter().map(|f| {
346                    let name = &f.ident;
347                    quote! {
348                        core::ptr::addr_of_mut!((*ptr).#name).write(self.#name);
349                    }
350                }),
351                _ => unimplemented!(),
352            };
353            let name = &item_struct.ident;
354            quote! {
355                impl Clone for #name {
356                    // Clippy lint `incorrect_clone_impl_on_copy_type` requires that clone
357                    // implementations on `Copy` types are simply wrappers of `Copy`.
358                    // This is not the case here, and intentionally so because we want to
359                    // guarantee zeroed padding.
360                    fn clone(&self) -> Self {
361                        let mut value = core::mem::MaybeUninit::<Self>::uninit();
362                        unsafe {
363                            core::ptr::write_bytes(&mut value, 0, 1);
364                            let ptr = value.as_mut_ptr();
365                            #(#clone_statements)*
366                            value.assume_init()
367                        }
368                    }
369                }
370            }
371        }
372        _ => unimplemented!(),
373    }
374    .into()
375}