Skip to main content

anchor_attribute_account/
lib.rs

1extern crate proc_macro;
2
3use {
4    anchor_syn::{codegen::program::common::gen_discriminator, Overrides},
5    quote::{quote, ToTokens},
6    syn::{
7        parenthesized,
8        parse::{Parse, ParseStream},
9        parse_macro_input,
10        token::Paren,
11        Ident, LitStr, Token,
12    },
13};
14
15mod id;
16
17#[cfg(feature = "lazy-account")]
18mod lazy;
19
20/// An attribute for a data structure representing a Solana account.
21///
22/// `#[account]` generates trait implementations for the following traits:
23///
24/// - [`AccountSerialize`](./trait.AccountSerialize.html)
25/// - [`AccountDeserialize`](./trait.AccountDeserialize.html)
26/// - [`AnchorSerialize`](./trait.AnchorSerialize.html)
27/// - [`AnchorDeserialize`](./trait.AnchorDeserialize.html)
28/// - [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html)
29/// - [`Discriminator`](./trait.Discriminator.html)
30/// - [`Owner`](./trait.Owner.html)
31///
32/// When implementing account serialization traits the first 8 bytes are
33/// reserved for a unique account discriminator by default, self described by
34/// the first 8 bytes of the SHA256 of the account's Rust ident. This is unless
35/// the discriminator was overridden with the `discriminator` argument (see
36/// [Arguments](#arguments)).
37///
38/// As a result, any calls to `AccountDeserialize`'s `try_deserialize` will
39/// check this discriminator. If it doesn't match, an invalid account was given,
40/// and the account deserialization will exit with an error.
41///
42/// # Arguments
43///
44/// - `discriminator`: Override the default 8-byte discriminator
45///
46///     **Usage:** `discriminator = <CONST_EXPR>`
47///
48///     All constant expressions are supported.
49///
50///     **Examples:**
51///
52///     - `discriminator = 1` (shortcut for `[1]`)
53///     - `discriminator = [1, 2, 3, 4]`
54///     - `discriminator = b"hi"`
55///     - `discriminator = MY_DISC`
56///     - `discriminator = get_disc(...)`
57///
58/// # Zero Copy Deserialization
59///
60/// **WARNING**: Zero copy deserialization is an experimental feature. It's
61/// recommended to use it only when necessary, i.e., when you have extremely
62/// large accounts that cannot be Borsh deserialized without hitting stack or
63/// heap limits.
64///
65/// ## Usage
66///
67/// To enable zero-copy-deserialization, one can pass in the `zero_copy`
68/// argument to the macro as follows:
69///
70/// ```ignore
71/// #[account(zero_copy)]
72/// ```
73///
74/// This can be used to conveniently implement
75/// [`ZeroCopy`](./trait.ZeroCopy.html) so that the account can be used
76/// with [`AccountLoader`](./accounts/account_loader/struct.AccountLoader.html).
77///
78/// Other than being more efficient, the most salient benefit this provides is
79/// the ability to define account types larger than the max stack or heap size.
80/// When using borsh, the account has to be copied and deserialized into a new
81/// data structure and thus is constrained by stack and heap limits imposed by
82/// the BPF VM. With zero copy deserialization, all bytes from the account's
83/// backing `RefCell<&mut [u8]>` are simply re-interpreted as a reference to
84/// the data structure. No allocations or copies necessary. Hence the ability
85/// to get around stack and heap limitations.
86///
87/// To facilitate this, all fields in an account must be constrained to be
88/// "plain old  data", i.e., they must implement
89/// [`Pod`](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html). Please review the
90/// [`safety`](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html#safety)
91/// section before using.
92///
93/// Using `zero_copy` requires adding the following dependency to your `Cargo.toml` file:
94///
95/// ```toml
96/// bytemuck = { version = "1.17", features = ["derive", "min_const_generics"] }
97/// ```
98#[proc_macro_attribute]
99pub fn account(
100    args: proc_macro::TokenStream,
101    input: proc_macro::TokenStream,
102) -> proc_macro::TokenStream {
103    let args = parse_macro_input!(args as AccountArgs);
104    let namespace = args.namespace.unwrap_or_default();
105    let is_zero_copy = args.zero_copy.is_some();
106    let unsafe_bytemuck = args.zero_copy.unwrap_or_default();
107
108    let account_strct = parse_macro_input!(input as syn::ItemStruct);
109    let account_name = &account_strct.ident;
110    let account_name_str = account_name.to_string();
111    let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
112
113    let discriminator = args
114        .overrides
115        .and_then(|ov| ov.discriminator)
116        .unwrap_or_else(|| {
117            // Namespace the discriminator to prevent collisions.
118            let namespace = if namespace.is_empty() {
119                "account"
120            } else {
121                &namespace
122            };
123
124            gen_discriminator(namespace, account_name)
125        });
126    let disc = if account_strct.generics.lt_token.is_some() {
127        quote! { #account_name::#type_gen::DISCRIMINATOR }
128    } else {
129        quote! { #account_name::DISCRIMINATOR }
130    };
131
132    let owner_impl = {
133        if namespace.is_empty() {
134            quote! {
135                #[automatically_derived]
136                impl #impl_gen anchor_lang::Owner for #account_name #type_gen #where_clause {
137                    fn owner() -> Pubkey {
138                        // In a doctest the ID will be in the current scope, not the crate root
139                        #[cfg(not(doctest))]
140                        { crate::ID }
141                        #[cfg(doctest)]
142                        { ID }
143                    }
144                }
145            }
146        } else {
147            quote! {}
148        }
149    };
150
151    let unsafe_bytemuck_impl = {
152        if unsafe_bytemuck {
153            quote! {
154                #[automatically_derived]
155                unsafe impl #impl_gen anchor_lang::__private::bytemuck::Pod for #account_name #type_gen #where_clause {}
156                #[automatically_derived]
157                unsafe impl #impl_gen anchor_lang::__private::bytemuck::Zeroable for #account_name #type_gen #where_clause {}
158            }
159        } else {
160            quote! {}
161        }
162    };
163
164    let bytemuck_derives = {
165        if !unsafe_bytemuck {
166            quote! {
167                #[zero_copy]
168            }
169        } else {
170            quote! {
171                #[zero_copy(unsafe)]
172            }
173        }
174    };
175
176    proc_macro::TokenStream::from({
177        if is_zero_copy {
178            quote! {
179                #bytemuck_derives
180                #account_strct
181
182                #unsafe_bytemuck_impl
183
184                #[automatically_derived]
185                impl #impl_gen anchor_lang::ZeroCopy for #account_name #type_gen #where_clause {}
186
187                #[automatically_derived]
188                impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
189                    const DISCRIMINATOR: &'static [u8] = #discriminator;
190                }
191
192                // This trait is useful for clients deserializing accounts.
193                // It's expected on-chain programs deserialize via zero-copy.
194                #[automatically_derived]
195                impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
196                    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
197                        if buf.len() < #disc.len() {
198                            return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
199                        }
200                        let given_disc = &buf[..#disc.len()];
201                        if #disc != given_disc {
202                            return Err(anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch).with_account_name(#account_name_str));
203                        }
204                        Self::try_deserialize_unchecked(buf)
205                    }
206
207                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
208                        let data: &[u8] = &buf[#disc.len()..];
209                        Ok(anchor_lang::__private::bytemuck::pod_read_unaligned(data))
210                    }
211                }
212
213                #owner_impl
214            }
215        } else {
216            let lazy = {
217                #[cfg(feature = "lazy-account")]
218                match namespace.is_empty().then(|| lazy::gen_lazy(&account_strct)) {
219                    Some(Ok(lazy)) => lazy,
220                    // If lazy codegen fails for whatever reason, return empty tokenstream which
221                    // will make the account unusable with `LazyAccount<T>`
222                    _ => Default::default(),
223                }
224                #[cfg(not(feature = "lazy-account"))]
225                proc_macro2::TokenStream::default()
226            };
227            quote! {
228                #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
229                #account_strct
230
231                #[automatically_derived]
232                impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
233                    fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
234                        if writer.write_all(#disc).is_err() {
235                            return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
236                        }
237
238                        if AnchorSerialize::serialize(self, writer).is_err() {
239                            return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
240                        }
241                        Ok(())
242                    }
243                }
244
245                #[automatically_derived]
246                impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
247                    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
248                        if buf.len() < #disc.len() {
249                            return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
250                        }
251                        let given_disc = &buf[..#disc.len()];
252                        if #disc != given_disc {
253                            return Err(anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch).with_account_name(#account_name_str));
254                        }
255                        Self::try_deserialize_unchecked(buf)
256                    }
257
258                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
259                        let mut data: &[u8] = &buf[#disc.len()..];
260                        AnchorDeserialize::deserialize(&mut data)
261                            .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
262                    }
263                }
264
265                #[automatically_derived]
266                impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
267                    const DISCRIMINATOR: &'static [u8] = #discriminator;
268                }
269
270                #owner_impl
271
272                #lazy
273            }
274        }
275    })
276}
277
278#[derive(Debug, Default)]
279struct AccountArgs {
280    /// `bool` is for deciding whether to use `unsafe` e.g. `Some(true)` for `zero_copy(unsafe)`
281    zero_copy: Option<bool>,
282    /// Account namespace override, `account` if not specified
283    namespace: Option<String>,
284    /// Named overrides
285    overrides: Option<Overrides>,
286}
287
288impl Parse for AccountArgs {
289    fn parse(input: ParseStream) -> syn::Result<Self> {
290        let mut parsed = Self::default();
291        let args = input.parse_terminated(AccountArg::parse, Token![,])?;
292        for arg in args {
293            match arg {
294                AccountArg::ZeroCopy { is_unsafe } => {
295                    parsed.zero_copy.replace(is_unsafe);
296                }
297                AccountArg::Namespace(ns) => {
298                    parsed.namespace.replace(ns);
299                }
300                AccountArg::Overrides(ov) => {
301                    parsed.overrides.replace(ov);
302                }
303            }
304        }
305
306        Ok(parsed)
307    }
308}
309
310enum AccountArg {
311    ZeroCopy { is_unsafe: bool },
312    Namespace(String),
313    Overrides(Overrides),
314}
315
316impl Parse for AccountArg {
317    fn parse(input: ParseStream) -> syn::Result<Self> {
318        // Namespace
319        if let Ok(ns) = input.parse::<LitStr>() {
320            return Ok(Self::Namespace(
321                ns.to_token_stream().to_string().replace('\"', ""),
322            ));
323        }
324
325        // Zero copy
326        if input
327            .fork()
328            .parse::<Ident>()
329            .is_ok_and(|ident| ident == "zero_copy")
330        {
331            input.parse::<Ident>()?;
332            let is_unsafe = if input.peek(Paren) {
333                let content;
334                parenthesized!(content in input);
335                let content = content.parse::<proc_macro2::TokenStream>()?;
336                if content.to_string().as_str().trim() != "unsafe" {
337                    return Err(syn::Error::new(
338                        syn::spanned::Spanned::span(&content),
339                        "Expected `unsafe`",
340                    ));
341                }
342                true
343            } else {
344                false
345            };
346
347            return Ok(Self::ZeroCopy { is_unsafe });
348        }
349
350        // Overrides (handles discriminator = ...)
351        // This will catch invalid arguments like `size = 1234` and provide
352        // an informative error message via Overrides::parse
353        input.parse::<Overrides>().map(Self::Overrides)
354    }
355}
356
357#[proc_macro_derive(ZeroCopyAccessor, attributes(accessor))]
358pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
359    let account_strct = parse_macro_input!(item as syn::ItemStruct);
360    let account_name = &account_strct.ident;
361    let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
362
363    let fields = match &account_strct.fields {
364        syn::Fields::Named(n) => n,
365        _ => {
366            return syn::Error::new_spanned(
367                &account_strct.ident,
368                "#[derive(ZeroCopyAccessor)] requires a struct with named fields",
369            )
370            .into_compile_error()
371            .into()
372        }
373    };
374    let methods: Vec<proc_macro2::TokenStream> = fields
375        .named
376        .iter()
377        .filter_map(|field: &syn::Field| {
378            field
379                .attrs
380                .iter()
381                .find(|attr| anchor_syn::parser::tts_to_string(attr.path()) == "accessor")
382                .map(|attr| {
383                    let tokens = match &attr.meta {
384                        syn::Meta::List(list) => list.tokens.clone(),
385                        _ => {
386                            return syn::Error::new_spanned(
387                                attr,
388                                "`#[accessor]` requires a type argument, e.g `#[accessor(MyType)]`",
389                            )
390                            .into_compile_error();
391                        }
392                    };
393                    let accessor_ty = match tokens.into_iter().next() {
394                        Some(token) => token,
395                        None => {
396                            return syn::Error::new_spanned(
397                                attr,
398                                "`#[accessor]` requires a type inside the parentheses e.g \
399                                 `#[accessor(MyType)]`",
400                            )
401                            .into_compile_error()
402                        }
403                    };
404
405                    #[allow(
406                        clippy::unwrap_used,
407                        reason = "accessor fields always have idents (named struct fields)"
408                    )]
409                    let field_name = field.ident.as_ref().unwrap();
410                    #[allow(
411                        clippy::unwrap_used,
412                        reason = "get_<field_name> formed from a valid Rust identifier is always \
413                                  valid TokenStream"
414                    )]
415                    let get_field: proc_macro2::TokenStream =
416                        format!("get_{field_name}").parse().unwrap();
417                    #[allow(
418                        clippy::unwrap_used,
419                        reason = "set_<field_name> formed from a valid Rust identifier is always \
420                                  valid TokenStream"
421                    )]
422                    let set_field: proc_macro2::TokenStream =
423                        format!("set_{field_name}").parse().unwrap();
424
425                    quote! {
426                        pub fn #get_field(&self) -> #accessor_ty {
427                            anchor_lang::__private::ZeroCopyAccessor::get(&self.#field_name)
428                        }
429                        pub fn #set_field(&mut self, input: &#accessor_ty) {
430                            self.#field_name = anchor_lang::__private::ZeroCopyAccessor::set(input);
431                        }
432                    }
433                })
434        })
435        .collect();
436    proc_macro::TokenStream::from(quote! {
437        #[automatically_derived]
438        impl #impl_gen #account_name #ty_gen #where_clause {
439            #(#methods)*
440        }
441    })
442}
443
444/// A data structure that can be used as an internal field for a zero copy
445/// deserialized account, i.e., a struct marked with `#[account(zero_copy)]`.
446///
447/// `#[zero_copy]` is just a convenient alias for
448///
449/// ```ignore
450/// #[derive(Copy, Clone)]
451/// #[derive(bytemuck::Zeroable)]
452/// #[derive(bytemuck::Pod)]
453/// #[repr(C)]
454/// struct MyStruct {...}
455/// ```
456#[proc_macro_attribute]
457pub fn zero_copy(
458    args: proc_macro::TokenStream,
459    item: proc_macro::TokenStream,
460) -> proc_macro::TokenStream {
461    let mut is_unsafe = false;
462    for arg in args.into_iter() {
463        match arg {
464            proc_macro::TokenTree::Ident(ident) => {
465                if ident.to_string() == "unsafe" {
466                    // `#[zero_copy(unsafe)]` maintains the old behaviour
467                    //
468                    // ```ignore
469                    // #[derive(Copy, Clone)]
470                    // #[repr(packed)]
471                    // struct MyStruct {...}
472                    // ```
473                    is_unsafe = true;
474                } else {
475                    return syn::Error::new(
476                        proc_macro2::Span::from(ident.span()),
477                        "expected `unsafe`, e.g `#[zero_copy(unsafe)]`",
478                    )
479                    .into_compile_error()
480                    .into();
481                }
482            }
483            _ => {
484                return syn::Error::new(
485                    proc_macro2::Span::from(arg.span()),
486                    "expected `unsafe`, e.g `#[zero_copy(unsafe)]`",
487                )
488                .into_compile_error()
489                .into();
490            }
491        }
492    }
493
494    let account_strct = parse_macro_input!(item as syn::ItemStruct);
495
496    // Takes the first repr. It's assumed that more than one are not on the
497    // struct.
498    let attr = account_strct
499        .attrs
500        .iter()
501        .find(|attr| anchor_syn::parser::tts_to_string(attr.path()) == "repr");
502
503    let repr = match attr {
504        // Users might want to manually specify repr modifiers e.g. repr(C, packed)
505        Some(_attr) => quote! {},
506        None => {
507            if is_unsafe {
508                quote! {#[repr(Rust, packed)]}
509            } else {
510                quote! {#[repr(C)]}
511            }
512        }
513    };
514
515    let mut has_pod_attr = false;
516    let mut has_zeroable_attr = false;
517    for attr in account_strct.attrs.iter() {
518        if !attr.path().is_ident("derive") {
519            continue;
520        }
521        if let syn::Meta::List(list) = &attr.meta {
522            let tokens_str = list.tokens.to_string();
523            if tokens_str.contains("bytemuck :: Pod") {
524                has_pod_attr = true;
525            }
526            if tokens_str.contains("bytemuck :: Zeroable") {
527                has_zeroable_attr = true;
528            }
529        }
530    }
531
532    // Once the Pod derive macro is expanded the compiler has to use the local crate's
533    // bytemuck `::bytemuck::Pod` anyway, so we're no longer using the privately
534    // exported anchor bytemuck `__private::bytemuck`, so that there won't be any
535    // possible disparity between the anchor version and the local crate's version.
536    let pod = if has_pod_attr || is_unsafe {
537        quote! {}
538    } else {
539        quote! {#[derive(::bytemuck::Pod)]}
540    };
541    let zeroable = if has_zeroable_attr || is_unsafe {
542        quote! {}
543    } else {
544        quote! {#[derive(::bytemuck::Zeroable)]}
545    };
546
547    let ret = quote! {
548        #[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
549        #repr
550        #pod
551        #zeroable
552        #account_strct
553    };
554
555    #[cfg(feature = "idl-build")]
556    {
557        let derive_unsafe = if is_unsafe {
558            // Not a real proc-macro but exists in order to pass the serialization info
559            quote! { #[derive(bytemuck::Unsafe)] }
560        } else {
561            quote! {}
562        };
563
564        let zc_struct = syn::parse_quote! {
565            #derive_unsafe
566            #ret
567        };
568        let idl_build_impl = anchor_syn::idl::impl_idl_build_struct(&zc_struct);
569        return proc_macro::TokenStream::from(quote! {
570            #ret
571            #idl_build_impl
572        });
573    }
574
575    #[allow(unreachable_code)]
576    proc_macro::TokenStream::from(ret)
577}
578
579/// Convenience macro to define a static public key.
580///
581/// Input: a single literal base58 string representation of a Pubkey.
582#[proc_macro]
583pub fn pubkey(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
584    let pk = parse_macro_input!(input as id::Pubkey);
585    proc_macro::TokenStream::from(quote! {#pk})
586}
587
588/// Defines the program's ID. This should be used at the root of all Anchor
589/// based programs.
590#[proc_macro]
591pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
592    #[cfg(feature = "idl-build")]
593    let address = input.clone().to_string();
594
595    let id = parse_macro_input!(input as id::Id);
596    let ret = quote! { #id };
597
598    #[cfg(feature = "idl-build")]
599    {
600        let idl_print = anchor_syn::idl::gen_idl_print_fn_address(address);
601        return proc_macro::TokenStream::from(quote! {
602            #ret
603            #idl_print
604        });
605    }
606
607    #[allow(unreachable_code)]
608    proc_macro::TokenStream::from(ret)
609}