Skip to main content

anchor_syn/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3pub mod codegen;
4pub mod parser;
5
6#[cfg(feature = "idl-build")]
7pub mod idl;
8
9#[cfg(feature = "hash")]
10pub mod hash;
11#[cfg(not(feature = "hash"))]
12pub(crate) mod hash;
13
14use {
15    codegen::{accounts as accounts_codegen, program as program_codegen},
16    parser::{accounts as accounts_parser, program as program_parser},
17    proc_macro2::{Span, TokenStream},
18    quote::{quote, ToTokens},
19    std::{collections::HashMap, ops::Deref},
20    syn::{
21        ext::IdentExt,
22        parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult},
23        parse_quote,
24        punctuated::Punctuated,
25        spanned::Spanned,
26        token::Comma,
27        Attribute, Expr, ExprLit, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, Lit,
28        LitInt, PatType, Token, Type, TypePath,
29    },
30};
31
32/// Generic result type, only used when [`syn::Error`] doesn't make sense.
33#[cfg(feature = "idl-build")]
34pub(crate) type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
35
36#[derive(Debug)]
37pub struct Program {
38    pub ixs: Vec<Ix>,
39    pub name: Ident,
40    pub docs: Option<Vec<String>>,
41    pub program_mod: ItemMod,
42    pub fallback_fn: Option<FallbackFn>,
43    pub program_args: Option<ProgramArgs>,
44}
45
46impl Parse for Program {
47    fn parse(input: ParseStream) -> ParseResult<Self> {
48        let program_mod = <ItemMod as Parse>::parse(input)?;
49        program_parser::parse(program_mod)
50    }
51}
52
53impl From<&Program> for TokenStream {
54    fn from(program: &Program) -> Self {
55        program_codegen::generate(program)
56    }
57}
58
59impl ToTokens for Program {
60    fn to_tokens(&self, tokens: &mut TokenStream) {
61        tokens.extend::<TokenStream>(self.into());
62    }
63}
64
65#[derive(Debug, Default)]
66pub struct ProgramArgs {
67    legacy_idl: bool,
68}
69
70impl Parse for ProgramArgs {
71    fn parse(input: ParseStream) -> ParseResult<Self> {
72        let mut parsed = Self::default();
73        let args = input.parse_terminated(Ident::parse, Token![,])?;
74
75        for arg in args {
76            match arg.to_string().as_str() {
77                "legacy_idl" => {
78                    if parsed.legacy_idl {
79                        return Err(syn::Error::new(
80                            arg.span(),
81                            "Duplicate `legacy_idl` argument",
82                        ));
83                    }
84                    parsed.legacy_idl = true;
85                }
86                name => {
87                    return Err(syn::Error::new(
88                        arg.span(),
89                        format!("Invalid argument `{name}`. Expected one of: `legacy_idl`"),
90                    ));
91                }
92            }
93        }
94
95        Ok(parsed)
96    }
97}
98
99#[derive(Debug)]
100pub struct Ix {
101    pub raw_method: ItemFn,
102    pub ident: Ident,
103    pub docs: Option<Vec<String>>,
104    pub cfgs: Vec<Attribute>,
105    pub args: Vec<IxArg>,
106    pub returns: IxReturn,
107    // The ident for the struct deriving Accounts.
108    pub anchor_ident: Ident,
109    /// Overrides coming from the `#[instruction]` attribute
110    pub overrides: Option<Overrides>,
111}
112
113/// Common overrides for the `#[instruction]`, `#[account]` and `#[event]` attributes
114#[derive(Debug, Default)]
115pub struct Overrides {
116    /// Override the default 8-byte discriminator
117    // `Box` is used to avoid large memory use in the common case as `Expr` is a large type
118    pub discriminator: Option<Box<Expr>>,
119}
120
121impl Parse for Overrides {
122    fn parse(input: ParseStream) -> ParseResult<Self> {
123        let mut attr = Self::default();
124        let args = input.parse_terminated(NamedArg::parse, Token![,])?;
125        for arg in args {
126            match arg.name.to_string().as_str() {
127                "discriminator" => {
128                    let value = match arg.value {
129                        // Allow `discriminator = 42`
130                        Expr::Lit(ExprLit {
131                            lit: lit @ Lit::Int(_),
132                            ..
133                        }) => {
134                            parse_quote!(&[#lit])
135                        }
136                        // Allow `discriminator = [0, 1, 2, 3]`
137                        Expr::Array(arr) => {
138                            parse_quote!(&#arr)
139                        }
140                        expr => expr,
141                    };
142                    attr.discriminator.replace(Box::new(value))
143                }
144                name => {
145                    return Err(ParseError::new(
146                        arg.name.span(),
147                        format!(
148                            "Invalid argument `{}`. Expected one of: `discriminator`",
149                            name
150                        ),
151                    ));
152                }
153            };
154        }
155
156        Ok(attr)
157    }
158}
159
160struct NamedArg {
161    name: Ident,
162    #[allow(dead_code)]
163    eq_token: Token![=],
164    value: Expr,
165}
166
167impl Parse for NamedArg {
168    fn parse(input: ParseStream) -> ParseResult<Self> {
169        Ok(Self {
170            name: input.parse()?,
171            eq_token: input.parse()?,
172            value: input.parse()?,
173        })
174    }
175}
176
177#[derive(Debug)]
178pub struct IxArg {
179    pub name: Ident,
180    pub docs: Option<Vec<String>>,
181    pub raw_arg: PatType,
182}
183
184#[derive(Debug)]
185pub struct IxReturn {
186    pub ty: Type,
187}
188
189#[derive(Debug)]
190pub struct FallbackFn {
191    raw_method: ItemFn,
192}
193
194#[derive(Debug)]
195pub struct AccountsStruct {
196    // Name of the accounts struct.
197    pub ident: Ident,
198    // Generics + lifetimes on the accounts struct.
199    pub generics: Generics,
200    // Fields on the accounts struct.
201    pub fields: Vec<AccountField>,
202    // Instruction data api expression.
203    instruction_api: Option<Punctuated<syn::FnArg, Comma>>,
204}
205
206impl Parse for AccountsStruct {
207    fn parse(input: ParseStream) -> ParseResult<Self> {
208        let strct = <ItemStruct as Parse>::parse(input)?;
209        accounts_parser::parse(&strct)
210    }
211}
212
213impl From<&AccountsStruct> for TokenStream {
214    fn from(accounts: &AccountsStruct) -> Self {
215        accounts_codegen::generate(accounts)
216    }
217}
218
219impl ToTokens for AccountsStruct {
220    fn to_tokens(&self, tokens: &mut TokenStream) {
221        tokens.extend::<TokenStream>(self.into());
222    }
223}
224
225impl AccountsStruct {
226    pub fn new(
227        strct: ItemStruct,
228        fields: Vec<AccountField>,
229        instruction_api: Option<Punctuated<syn::FnArg, Comma>>,
230    ) -> Self {
231        let ident = strct.ident.clone();
232        let generics = strct.generics;
233        Self {
234            ident,
235            generics,
236            fields,
237            instruction_api,
238        }
239    }
240
241    // Return value maps instruction name to type.
242    // E.g. if we have `#[instruction(data: u64)]` then returns
243    // { "data": "u64"}.
244    pub fn instruction_args(&self) -> Option<HashMap<String, String>> {
245        self.instruction_api.as_ref().map(|instruction_api| {
246            instruction_api
247                .iter()
248                .map(|expr| {
249                    let arg = parser::tts_to_string(expr);
250                    let components: Vec<&str> = arg.split(" : ").collect();
251                    assert!(components.len() == 2);
252                    #[allow(
253                        clippy::indexing_slicing,
254                        reason = "len == 2 asserted immediately above"
255                    )]
256                    let result = (components[0].to_string(), components[1].to_string());
257                    result
258                })
259                .collect()
260        })
261    }
262
263    pub fn field_names(&self) -> Vec<String> {
264        self.fields
265            .iter()
266            .map(|field| field.ident().to_string())
267            .collect()
268    }
269
270    pub fn has_optional(&self) -> bool {
271        for field in &self.fields {
272            if let AccountField::Field(field) = field {
273                if field.is_optional {
274                    return true;
275                }
276            }
277        }
278        false
279    }
280
281    pub fn is_field_optional<T: quote::ToTokens>(&self, field: &T) -> bool {
282        let matching_field = self
283            .fields
284            .iter()
285            .find(|f| *f.ident() == parser::tts_to_string(field));
286        if let Some(matching_field) = matching_field {
287            matching_field.is_optional()
288        } else {
289            false
290        }
291    }
292}
293
294#[allow(clippy::large_enum_variant)]
295#[derive(Debug)]
296pub enum AccountField {
297    Field(Field),
298    CompositeField(CompositeField),
299}
300
301impl AccountField {
302    fn ident(&self) -> &Ident {
303        match self {
304            AccountField::Field(field) => &field.ident,
305            AccountField::CompositeField(c_field) => &c_field.ident,
306        }
307    }
308
309    fn is_optional(&self) -> bool {
310        match self {
311            AccountField::Field(field) => field.is_optional,
312            AccountField::CompositeField(_) => false,
313        }
314    }
315
316    pub fn ty_name(&self) -> Option<String> {
317        let qualified_ty_name = match self {
318            AccountField::Field(field) => match &field.ty {
319                Ty::Account(account) => Some(parser::tts_to_string(&account.account_type_path)),
320                Ty::LazyAccount(account) => Some(parser::tts_to_string(&account.account_type_path)),
321                _ => None,
322            },
323            AccountField::CompositeField(field) => Some(field.symbol.clone()),
324        };
325
326        qualified_ty_name.map(|name| match name.rsplit_once(" :: ") {
327            Some((_prefix, suffix)) => suffix.to_string(),
328            None => name,
329        })
330    }
331}
332
333#[derive(Debug)]
334pub struct Field {
335    pub ident: Ident,
336    pub constraints: ConstraintGroup,
337    pub ty: Ty,
338    pub is_optional: bool,
339    pub ty_span: Span,
340    /// IDL Doc comment
341    pub docs: Option<Vec<String>>,
342}
343
344impl Field {
345    pub fn typed_ident(&self) -> proc_macro2::TokenStream {
346        let name = &self.ident;
347        let ty_decl = self.ty_decl(false);
348        quote! {
349            #name: #ty_decl
350        }
351    }
352
353    pub fn ty_decl(&self, ignore_option: bool) -> proc_macro2::TokenStream {
354        let account_ty = self.account_ty();
355        let container_ty = self.container_ty();
356        let inner_ty = match &self.ty {
357            Ty::AccountInfo => quote! {
358                AccountInfo
359            },
360            Ty::UncheckedAccount => quote! {
361                UncheckedAccount
362            },
363            Ty::Signer => quote! {
364                Signer
365            },
366            Ty::ProgramData => quote! {
367                ProgramData
368            },
369            Ty::SystemAccount => quote! {
370                SystemAccount
371            },
372            Ty::Account(AccountTy { boxed, .. })
373            | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
374                if *boxed {
375                    quote! {
376                        Box<#container_ty<#account_ty>>
377                    }
378                } else {
379                    quote! {
380                        #container_ty<#account_ty>
381                    }
382                }
383            }
384            Ty::Sysvar(ty) => {
385                let account = match ty {
386                    SysvarTy::Clock => quote! {Clock},
387                    SysvarTy::Rent => quote! {Rent},
388                    SysvarTy::EpochSchedule => quote! {EpochSchedule},
389                    SysvarTy::Fees => quote! {Fees},
390                    SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
391                    SysvarTy::SlotHashes => quote! {SlotHashes},
392                    SysvarTy::SlotHistory => quote! {SlotHistory},
393                    SysvarTy::StakeHistory => quote! {StakeHistory},
394                    SysvarTy::Instructions => quote! {Instructions},
395                    SysvarTy::Rewards => quote! {Rewards},
396                };
397                quote! {
398                    Sysvar<#account>
399                }
400            }
401            Ty::Program(ty) => {
402                let program = &ty.account_type_path;
403                // Check if this is the generic Program<'info> (unit type)
404                let program_str = quote!(#program).to_string();
405                if program_str == "__SolanaProgramUnitType" {
406                    quote! {
407                        #container_ty<'info>
408                    }
409                } else {
410                    quote! {
411                        #container_ty<'info, #program>
412                    }
413                }
414            }
415            Ty::Migration(ty) => {
416                let from = &ty.from_type_path;
417                let to = &ty.to_type_path;
418                quote! {
419                    #container_ty<'info, #from, #to>
420                }
421            }
422            _ => quote! {
423                #container_ty<#account_ty>
424            },
425        };
426        if self.is_optional && !ignore_option {
427            quote! {
428                Option<#inner_ty>
429            }
430        } else {
431            quote! {
432                #inner_ty
433            }
434        }
435    }
436
437    // Ignores optional accounts. Optional account checks and handing should be done prior to this
438    // function being called.
439    pub fn from_account_info(
440        &self,
441        kind: Option<&InitKind>,
442        checked: bool,
443    ) -> proc_macro2::TokenStream {
444        let field = &self.ident;
445        let field_str = field.to_string();
446        let container_ty = self.container_ty();
447        let program_id = crate::codegen::private_ident("__program_id");
448        let owner_addr = match &kind {
449            None => quote! { #program_id },
450            Some(InitKind::Program { .. }) => quote! {
451                #program_id
452            },
453            _ => quote! {
454                &anchor_spl::token::ID
455            },
456        };
457        match &self.ty {
458            Ty::AccountInfo => quote! { #field.to_account_info() },
459            Ty::UncheckedAccount => {
460                quote! { UncheckedAccount::try_from(&#field) }
461            }
462            Ty::Account(AccountTy { boxed, .. })
463            | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
464                let stream = if checked {
465                    quote! {
466                        match #container_ty::try_from(&#field) {
467                            Ok(val) => val,
468                            Err(e) => return Err(e.with_account_name(#field_str))
469                        }
470                    }
471                } else {
472                    quote! {
473                        match #container_ty::try_from_unchecked(&#field) {
474                            Ok(val) => val,
475                            Err(e) => return Err(e.with_account_name(#field_str))
476                        }
477                    }
478                };
479                if *boxed {
480                    quote! {
481                        Box::new(#stream)
482                    }
483                } else {
484                    stream
485                }
486            }
487            Ty::LazyAccount(_) => {
488                if checked {
489                    quote! {
490                        match #container_ty::try_from(&#field) {
491                            Ok(val) => val,
492                            Err(e) => return Err(e.with_account_name(#field_str))
493                        }
494                    }
495                } else {
496                    quote! {
497                        match #container_ty::try_from_unchecked(&#field) {
498                            Ok(val) => val,
499                            Err(e) => return Err(e.with_account_name(#field_str))
500                        }
501                    }
502                }
503            }
504            Ty::AccountLoader(_) => {
505                if checked {
506                    quote! {
507                        match #container_ty::try_from(&#field) {
508                            Ok(val) => val,
509                            Err(e) => return Err(e.with_account_name(#field_str))
510                        }
511                    }
512                } else {
513                    quote! {
514                        match #container_ty::try_from_unchecked(#owner_addr, &#field) {
515                            Ok(val) => val,
516                            Err(e) => return Err(e.with_account_name(#field_str))
517                        }
518                    }
519                }
520            }
521            _ => {
522                if checked {
523                    quote! {
524                        match #container_ty::try_from(#owner_addr, &#field) {
525                            Ok(val) => val,
526                            Err(e) => return Err(e.with_account_name(#field_str))
527                        }
528                    }
529                } else {
530                    quote! {
531                        match #container_ty::try_from_unchecked(#owner_addr, &#field) {
532                            Ok(val) => val,
533                            Err(e) => return Err(e.with_account_name(#field_str))
534                        }
535                    }
536                }
537            }
538        }
539    }
540
541    pub fn container_ty(&self) -> proc_macro2::TokenStream {
542        match &self.ty {
543            Ty::Account(_) => quote! {
544                anchor_lang::accounts::account::Account
545            },
546            Ty::LazyAccount(_) => quote! {
547                anchor_lang::accounts::lazy_account::LazyAccount
548            },
549            Ty::AccountLoader(_) => quote! {
550                anchor_lang::accounts::account_loader::AccountLoader
551            },
552            Ty::Migration(_) => quote! {
553                anchor_lang::accounts::migration::Migration
554            },
555            Ty::Sysvar(_) => quote! { anchor_lang::accounts::sysvar::Sysvar },
556            Ty::Program(_) => quote! { anchor_lang::accounts::program::Program },
557            Ty::Interface(_) => quote! { anchor_lang::accounts::interface::Interface },
558            Ty::InterfaceAccount(_) => {
559                quote! { anchor_lang::accounts::interface_account::InterfaceAccount }
560            }
561            Ty::AccountInfo => quote! {},
562            Ty::UncheckedAccount => quote! {},
563            Ty::Signer => quote! {},
564            Ty::SystemAccount => quote! {},
565            Ty::ProgramData => quote! {},
566        }
567    }
568
569    // Returns the inner account struct type.
570    pub fn account_ty(&self) -> proc_macro2::TokenStream {
571        match &self.ty {
572            Ty::AccountInfo => quote! {
573                AccountInfo
574            },
575            Ty::UncheckedAccount => quote! {
576                UncheckedAccount
577            },
578            Ty::Signer => quote! {
579                Signer
580            },
581            Ty::SystemAccount => quote! {
582                SystemAccount
583            },
584            Ty::ProgramData => quote! {
585                ProgramData
586            },
587            Ty::Account(ty) => {
588                let ident = &ty.account_type_path;
589                quote! {
590                    #ident
591                }
592            }
593            Ty::LazyAccount(ty) => {
594                let ident = &ty.account_type_path;
595                quote! {
596                    #ident
597                }
598            }
599            Ty::InterfaceAccount(ty) => {
600                let ident = &ty.account_type_path;
601                quote! {
602                    #ident
603                }
604            }
605            Ty::AccountLoader(ty) => {
606                let ident = &ty.account_type_path;
607                quote! {
608                    #ident
609                }
610            }
611            Ty::Migration(ty) => {
612                // Return just the From type for IDL and other uses
613                let from = &ty.from_type_path;
614                quote! {
615                    #from
616                }
617            }
618            Ty::Sysvar(ty) => match ty {
619                SysvarTy::Clock => quote! {Clock},
620                SysvarTy::Rent => quote! {Rent},
621                SysvarTy::EpochSchedule => quote! {EpochSchedule},
622                SysvarTy::Fees => quote! {Fees},
623                SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
624                SysvarTy::SlotHashes => quote! {SlotHashes},
625                SysvarTy::SlotHistory => quote! {SlotHistory},
626                SysvarTy::StakeHistory => quote! {StakeHistory},
627                SysvarTy::Instructions => quote! {Instructions},
628                SysvarTy::Rewards => quote! {Rewards},
629            },
630            Ty::Program(ty) => {
631                let program = &ty.account_type_path;
632                // Check if this is the special marker for generic Program<'info> (unit type)
633                let program_str = quote!(#program).to_string();
634                if program_str == "__SolanaProgramUnitType" {
635                    quote! {}
636                } else {
637                    quote! {
638                        #program
639                    }
640                }
641            }
642            Ty::Interface(ty) => {
643                let program = &ty.account_type_path;
644                quote! {
645                    #program
646                }
647            }
648        }
649    }
650}
651
652#[derive(Debug)]
653pub struct CompositeField {
654    pub ident: Ident,
655    pub constraints: ConstraintGroup,
656    pub symbol: String,
657    pub raw_field: syn::Field,
658    /// IDL Doc comment
659    pub docs: Option<Vec<String>>,
660}
661
662// A type of an account field.
663#[derive(Debug, PartialEq, Eq)]
664pub enum Ty {
665    AccountInfo,
666    UncheckedAccount,
667    AccountLoader(AccountLoaderTy),
668    Sysvar(SysvarTy),
669    Account(AccountTy),
670    LazyAccount(LazyAccountTy),
671    Migration(MigrationTy),
672    Program(ProgramTy),
673    Interface(InterfaceTy),
674    InterfaceAccount(InterfaceAccountTy),
675    Signer,
676    SystemAccount,
677    ProgramData,
678}
679
680#[derive(Debug, PartialEq, Eq)]
681pub enum SysvarTy {
682    Clock,
683    Rent,
684    EpochSchedule,
685    Fees,
686    RecentBlockhashes,
687    SlotHashes,
688    SlotHistory,
689    StakeHistory,
690    Instructions,
691    Rewards,
692}
693
694#[derive(Debug, PartialEq, Eq)]
695pub struct AccountLoaderTy {
696    // The struct type of the account.
697    pub account_type_path: TypePath,
698}
699
700#[derive(Debug, PartialEq, Eq)]
701pub struct AccountTy {
702    // The struct type of the account.
703    pub account_type_path: TypePath,
704    // True if the account has been boxed via `Box<T>`.
705    pub boxed: bool,
706}
707
708#[derive(Debug, PartialEq, Eq)]
709pub struct LazyAccountTy {
710    // The struct type of the account.
711    pub account_type_path: TypePath,
712}
713
714#[derive(Debug, PartialEq, Eq)]
715pub struct MigrationTy {
716    // Migration<'info, From, To> - we need both From and To types
717    pub from_type_path: TypePath,
718    pub to_type_path: TypePath,
719}
720
721#[derive(Debug, PartialEq, Eq)]
722pub struct InterfaceAccountTy {
723    // The struct type of the account.
724    pub account_type_path: TypePath,
725    // True if the account has been boxed via `Box<T>`.
726    pub boxed: bool,
727}
728
729#[derive(Debug, PartialEq, Eq)]
730pub struct ProgramTy {
731    // The struct type of the account.
732    pub account_type_path: TypePath,
733}
734
735#[derive(Debug, PartialEq, Eq)]
736pub struct InterfaceTy {
737    // The struct type of the account.
738    pub account_type_path: TypePath,
739}
740
741#[derive(Debug)]
742pub struct Error {
743    pub name: String,
744    pub raw_enum: ItemEnum,
745    pub ident: Ident,
746    pub codes: Vec<ErrorCode>,
747    pub args: Option<ErrorArgs>,
748}
749
750#[derive(Debug)]
751pub struct ErrorArgs {
752    pub offset: LitInt,
753}
754
755impl Parse for ErrorArgs {
756    fn parse(stream: ParseStream) -> ParseResult<Self> {
757        let offset_span = stream.span();
758        let offset = stream.call(Ident::parse_any)?;
759        if offset.to_string().as_str() != "offset" {
760            return Err(ParseError::new(offset_span, "expected keyword offset"));
761        }
762        stream.parse::<Token![=]>()?;
763        let offset: LitInt = stream.parse()?;
764        Ok(ErrorArgs { offset })
765    }
766}
767
768#[derive(Debug)]
769pub struct ErrorCode {
770    pub id: u32,
771    pub ident: Ident,
772    pub msg: Option<String>,
773}
774
775// All well formed constraints on a single `Accounts` field.
776#[derive(Debug, Default, Clone)]
777pub struct ConstraintGroup {
778    pub init: Option<ConstraintInitGroup>,
779    pub zeroed: Option<ConstraintZeroed>,
780    pub mutable: Option<ConstraintMut>,
781    pub dup: Option<ConstraintDup>,
782    pub signer: Option<ConstraintSigner>,
783    pub owner: Option<ConstraintOwner>,
784    pub rent_exempt: Option<ConstraintRentExempt>,
785    pub seeds: Option<ConstraintSeedsGroup>,
786    pub executable: Option<ConstraintExecutable>,
787    pub has_one: Vec<ConstraintHasOne>,
788    pub raw: Vec<ConstraintRaw>,
789    pub close: Option<ConstraintClose>,
790    pub address: Option<ConstraintAddress>,
791    pub associated_token: Option<ConstraintAssociatedToken>,
792    pub token_account: Option<ConstraintTokenAccountGroup>,
793    pub mint: Option<ConstraintTokenMintGroup>,
794    pub realloc: Option<ConstraintReallocGroup>,
795}
796
797impl ConstraintGroup {
798    pub fn is_zeroed(&self) -> bool {
799        self.zeroed.is_some()
800    }
801
802    pub fn is_mutable(&self) -> bool {
803        self.mutable.is_some()
804    }
805
806    pub fn is_dup(&self) -> bool {
807        self.dup.is_some()
808    }
809
810    /// Returns `true` when the field has `#[account(init, ...)]` but **not**
811    /// `#[account(init_if_needed, ...)]`.
812    pub fn is_pure_init(&self) -> bool {
813        matches!(&self.init, Some(c) if !c.if_needed)
814    }
815
816    pub fn is_signer(&self) -> bool {
817        self.signer.is_some()
818    }
819
820    pub fn is_close(&self) -> bool {
821        self.close.is_some()
822    }
823}
824
825// A single account constraint *after* merging all tokens into a well formed
826// constraint. Some constraints like "seeds" are defined by multiple
827// tokens, so a merging phase is required.
828#[allow(clippy::large_enum_variant)]
829#[derive(Debug)]
830pub enum Constraint {
831    Init(ConstraintInitGroup),
832    Zeroed(ConstraintZeroed),
833    Mut(ConstraintMut),
834    Dup(ConstraintDup),
835    Signer(ConstraintSigner),
836    HasOne(ConstraintHasOne),
837    Raw(ConstraintRaw),
838    Owner(ConstraintOwner),
839    RentExempt(ConstraintRentExempt),
840    Seeds(ConstraintSeedsGroup),
841    AssociatedToken(ConstraintAssociatedToken),
842    Executable(ConstraintExecutable),
843    Close(ConstraintClose),
844    Address(ConstraintAddress),
845    TokenAccount(ConstraintTokenAccountGroup),
846    Mint(ConstraintTokenMintGroup),
847    Realloc(ConstraintReallocGroup),
848}
849
850// Constraint token is a single keyword in a `#[account(<TOKEN>)]` attribute.
851#[allow(clippy::large_enum_variant)]
852#[derive(Debug)]
853pub enum ConstraintToken {
854    Init(Context<ConstraintInit>),
855    Zeroed(Context<ConstraintZeroed>),
856    Mut(Context<ConstraintMut>),
857    Dup(Context<ConstraintDup>),
858    Signer(Context<ConstraintSigner>),
859    HasOne(Context<ConstraintHasOne>),
860    Raw(Context<ConstraintRaw>),
861    Owner(Context<ConstraintOwner>),
862    RentExempt(Context<ConstraintRentExempt>),
863    Seeds(Context<ConstraintSeeds>),
864    Executable(Context<ConstraintExecutable>),
865    Close(Context<ConstraintClose>),
866    Payer(Context<ConstraintPayer>),
867    Space(Context<ConstraintSpace>),
868    Address(Context<ConstraintAddress>),
869    TokenMint(Context<ConstraintTokenMint>),
870    TokenAuthority(Context<ConstraintTokenAuthority>),
871    TokenTokenProgram(Context<ConstraintTokenProgram>),
872    AssociatedTokenMint(Context<ConstraintTokenMint>),
873    AssociatedTokenAuthority(Context<ConstraintTokenAuthority>),
874    AssociatedTokenTokenProgram(Context<ConstraintTokenProgram>),
875    MintAuthority(Context<ConstraintMintAuthority>),
876    MintFreezeAuthority(Context<ConstraintMintFreezeAuthority>),
877    MintDecimals(Context<ConstraintMintDecimals>),
878    MintTokenProgram(Context<ConstraintTokenProgram>),
879    Bump(Context<ConstraintTokenBump>),
880    ProgramSeed(Context<ConstraintProgramSeed>),
881    Realloc(Context<ConstraintRealloc>),
882    ReallocPayer(Context<ConstraintReallocPayer>),
883    ReallocZero(Context<ConstraintReallocZero>),
884    // extensions
885    ExtensionGroupPointerAuthority(Context<ConstraintExtensionAuthority>),
886    ExtensionGroupPointerGroupAddress(Context<ConstraintExtensionGroupPointerGroupAddress>),
887    ExtensionGroupMemberPointerAuthority(Context<ConstraintExtensionAuthority>),
888    ExtensionGroupMemberPointerMemberAddress(
889        Context<ConstraintExtensionGroupMemberPointerMemberAddress>,
890    ),
891    ExtensionMetadataPointerAuthority(Context<ConstraintExtensionAuthority>),
892    ExtensionMetadataPointerMetadataAddress(
893        Context<ConstraintExtensionMetadataPointerMetadataAddress>,
894    ),
895    ExtensionCloseAuthority(Context<ConstraintExtensionAuthority>),
896    ExtensionTokenHookAuthority(Context<ConstraintExtensionAuthority>),
897    ExtensionTokenHookProgramId(Context<ConstraintExtensionTokenHookProgramId>),
898    ExtensionPermanentDelegate(Context<ConstraintExtensionPermanentDelegate>),
899    ExtensionPausableAuthority(Context<ConstraintExtensionAuthority>),
900}
901
902impl Parse for ConstraintToken {
903    fn parse(stream: ParseStream) -> ParseResult<Self> {
904        accounts_parser::constraints::parse_token(stream)
905    }
906}
907
908#[derive(Debug, Clone)]
909pub struct ConstraintInit {
910    pub if_needed: bool,
911}
912
913#[derive(Debug, Clone)]
914pub struct ConstraintInitIfNeeded {}
915
916#[derive(Debug, Clone)]
917pub struct ConstraintZeroed {}
918
919#[derive(Debug, Clone)]
920pub struct ConstraintMut {
921    pub error: Option<Expr>,
922}
923
924#[derive(Debug, Clone)]
925pub struct ConstraintDup {}
926
927#[derive(Debug, Clone)]
928pub struct ConstraintReallocGroup {
929    pub payer: Expr,
930    pub space: Expr,
931    pub zero: Expr,
932}
933
934#[derive(Debug, Clone)]
935pub struct ConstraintRealloc {
936    pub space: Expr,
937}
938
939#[derive(Debug, Clone)]
940pub struct ConstraintReallocPayer {
941    pub target: Expr,
942}
943
944#[derive(Debug, Clone)]
945pub struct ConstraintReallocZero {
946    pub zero: Expr,
947}
948
949#[derive(Debug, Clone)]
950pub struct ConstraintSigner {
951    pub error: Option<Expr>,
952}
953
954#[derive(Debug, Clone)]
955pub struct ConstraintHasOne {
956    pub join_target: Expr,
957    pub error: Option<Expr>,
958}
959
960#[derive(Debug, Clone)]
961pub struct ConstraintRaw {
962    pub raw: Expr,
963    pub error: Option<Expr>,
964}
965
966#[derive(Debug, Clone)]
967pub struct ConstraintOwner {
968    pub owner_address: Expr,
969    pub error: Option<Expr>,
970}
971
972#[derive(Debug, Clone)]
973pub struct ConstraintAddress {
974    pub address: Expr,
975    pub error: Option<Expr>,
976}
977
978#[derive(Debug, Clone)]
979pub enum ConstraintRentExempt {
980    Enforce,
981    Skip,
982}
983
984#[derive(Debug, Clone)]
985pub struct ConstraintInitGroup {
986    pub if_needed: bool,
987    pub seeds: Option<ConstraintSeedsGroup>,
988    pub payer: Expr,
989    pub space: Option<Expr>,
990    pub kind: InitKind,
991}
992
993/// Seeds can be written as a literal slice (`[ a, b ]`) or any
994/// expression that produces `&[&[u8]]` at run time.
995#[derive(Debug, Clone)]
996pub enum SeedsExpr {
997    /// Example: `[ b"prefix".as_ref(), key.as_ref() ]`
998    List(Punctuated<Expr, Token![,]>),
999    /// Example: `pda_seeds(key)`
1000    Expr(Box<Expr>),
1001}
1002
1003impl SeedsExpr {
1004    /// Return the underlying `Punctuated` if this is the `List` form.
1005    fn list_mut(&mut self) -> Option<&mut Punctuated<Expr, Token![,]>> {
1006        match self {
1007            SeedsExpr::List(list) => Some(list),
1008            SeedsExpr::Expr(_) => None,
1009        }
1010    }
1011
1012    /// Mirrors `Punctuated::pop`: removes and returns the last element and its
1013    /// trailing punctuation when this is the `List` variant. For the `Expr variant`,
1014    /// which represents a single non-list seed expression, returns `None` because
1015    /// there is no list to pop from.
1016    pub fn pop(&mut self) -> Option<syn::punctuated::Pair<Expr, Token![,]>> {
1017        self.list_mut()?.pop()
1018    }
1019
1020    pub fn push_value(&mut self, value: Expr) {
1021        if let Some(list) = self.list_mut() {
1022            list.push_value(value);
1023        }
1024    }
1025
1026    /// Mirrors `Punctuated::push_value`: pushes a value without punctuation onto
1027    /// the underlying list when this is the `List` variant. No-op for the `Expr`
1028    /// variant.
1029    pub fn is_empty(&self) -> bool {
1030        match self {
1031            SeedsExpr::List(list) => list.is_empty(),
1032            SeedsExpr::Expr(_) => false, // Treat as “one seed”
1033        }
1034    }
1035
1036    /// Immutable iteration over every seed expression, regardless of variant
1037    pub fn iter(&self) -> Box<dyn Iterator<Item = &Expr> + '_> {
1038        match self {
1039            SeedsExpr::List(list) => Box::new(list.iter()),
1040            SeedsExpr::Expr(expr) => Box::new(std::iter::once(expr.as_ref())),
1041        }
1042    }
1043
1044    /// The number of seeds represented: `list.len()` for `List` and `1` for `Expr`.
1045    pub fn len(&self) -> usize {
1046        match self {
1047            SeedsExpr::List(list) => list.len(),
1048            SeedsExpr::Expr(_) => 1,
1049        }
1050    }
1051}
1052
1053/// Allow `quote!{ #seeds }`
1054impl quote::ToTokens for SeedsExpr {
1055    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1056        match self {
1057            SeedsExpr::List(list) => list.to_tokens(tokens),
1058            SeedsExpr::Expr(expr) => expr.to_tokens(tokens),
1059        }
1060    }
1061}
1062
1063impl syn::parse::Parse for SeedsExpr {
1064    fn parse(stream: syn::parse::ParseStream) -> syn::parse::Result<Self> {
1065        if stream.peek(syn::token::Bracket) {
1066            let content;
1067            syn::bracketed!(content in stream);
1068            let mut list: Punctuated<Expr, Token![,]> =
1069                content.parse_terminated(Expr::parse, Token![,])?;
1070            list.pop_punct();
1071
1072            Ok(SeedsExpr::List(list))
1073        } else {
1074            Ok(SeedsExpr::Expr(Box::new(stream.parse()?)))
1075        }
1076    }
1077}
1078
1079#[derive(Debug, Clone)]
1080pub struct ConstraintSeedsGroup {
1081    pub is_init: bool,
1082    pub seeds: SeedsExpr,
1083    pub bump: Option<Expr>,         // None => bump was given without a target.
1084    pub program_seed: Option<Expr>, // None => use the current program's program_id.
1085}
1086
1087#[derive(Debug, Clone)]
1088pub struct ConstraintSeeds {
1089    pub seeds: SeedsExpr,
1090}
1091
1092#[derive(Debug, Clone)]
1093pub struct ConstraintExecutable {}
1094
1095#[derive(Debug, Clone)]
1096pub struct ConstraintPayer {
1097    pub target: Expr,
1098}
1099
1100#[derive(Debug, Clone)]
1101pub struct ConstraintSpace {
1102    pub space: Expr,
1103}
1104
1105// extension constraints
1106#[derive(Debug, Clone)]
1107pub struct ConstraintExtensionAuthority {
1108    pub authority: Expr,
1109}
1110
1111#[derive(Debug, Clone)]
1112pub struct ConstraintExtensionGroupPointerGroupAddress {
1113    pub group_address: Expr,
1114}
1115
1116#[derive(Debug, Clone)]
1117pub struct ConstraintExtensionGroupMemberPointerMemberAddress {
1118    pub member_address: Expr,
1119}
1120
1121#[derive(Debug, Clone)]
1122pub struct ConstraintExtensionMetadataPointerMetadataAddress {
1123    pub metadata_address: Expr,
1124}
1125
1126#[derive(Debug, Clone)]
1127pub struct ConstraintExtensionTokenHookProgramId {
1128    pub program_id: Expr,
1129}
1130
1131#[derive(Debug, Clone)]
1132pub struct ConstraintExtensionPermanentDelegate {
1133    pub permanent_delegate: Expr,
1134}
1135
1136#[derive(Debug, Clone)]
1137#[allow(clippy::large_enum_variant)]
1138pub enum InitKind {
1139    Program {
1140        owner: Option<Expr>,
1141    },
1142    Interface {
1143        owner: Option<Expr>,
1144    },
1145    // Owner for token and mint represents the authority. Not to be confused
1146    // with the owner of the AccountInfo.
1147    Token {
1148        owner: Expr,
1149        mint: Expr,
1150        token_program: Option<Expr>,
1151    },
1152    AssociatedToken {
1153        owner: Expr,
1154        mint: Expr,
1155        token_program: Option<Expr>,
1156    },
1157    Mint {
1158        owner: Expr,
1159        freeze_authority: Option<Expr>,
1160        decimals: Expr,
1161        token_program: Option<Expr>,
1162        // extensions
1163        group_pointer_authority: Option<Expr>,
1164        group_pointer_group_address: Option<Expr>,
1165        group_member_pointer_authority: Option<Expr>,
1166        group_member_pointer_member_address: Option<Expr>,
1167        metadata_pointer_authority: Option<Expr>,
1168        metadata_pointer_metadata_address: Option<Expr>,
1169        close_authority: Option<Expr>,
1170        permanent_delegate: Option<Expr>,
1171        transfer_hook_authority: Option<Expr>,
1172        transfer_hook_program_id: Option<Expr>,
1173        pausable_authority: Option<Expr>,
1174    },
1175}
1176
1177#[derive(Debug, Clone)]
1178pub struct ConstraintClose {
1179    pub sol_dest: Ident,
1180}
1181
1182#[derive(Debug, Clone)]
1183pub struct ConstraintTokenMint {
1184    pub mint: Expr,
1185}
1186
1187#[derive(Debug, Clone)]
1188pub struct ConstraintMintConfidentialTransferData {
1189    pub confidential_transfer_data: Expr,
1190}
1191
1192#[derive(Debug, Clone)]
1193pub struct ConstraintMintMetadata {
1194    pub token_metadata: Expr,
1195}
1196
1197#[derive(Debug, Clone)]
1198pub struct ConstraintMintTokenGroupData {
1199    pub token_group_data: Expr,
1200}
1201
1202#[derive(Debug, Clone)]
1203pub struct ConstraintMintTokenGroupMemberData {
1204    pub token_group_member_data: Expr,
1205}
1206
1207#[derive(Debug, Clone)]
1208pub struct ConstraintMintMetadataPointerData {
1209    pub metadata_pointer_data: Expr,
1210}
1211
1212#[derive(Debug, Clone)]
1213pub struct ConstraintMintGroupPointerData {
1214    pub group_pointer_data: Expr,
1215}
1216
1217#[derive(Debug, Clone)]
1218pub struct ConstraintMintGroupMemberPointerData {
1219    pub group_member_pointer_data: Expr,
1220}
1221
1222#[derive(Debug, Clone)]
1223pub struct ConstraintMintCloseAuthority {
1224    pub close_authority: Expr,
1225}
1226
1227#[derive(Debug, Clone)]
1228pub struct ConstraintTokenAuthority {
1229    pub auth: Expr,
1230}
1231
1232#[derive(Debug, Clone)]
1233pub struct ConstraintTokenProgram {
1234    token_program: Expr,
1235}
1236
1237#[derive(Debug, Clone)]
1238pub struct ConstraintMintAuthority {
1239    pub mint_auth: Expr,
1240}
1241
1242#[derive(Debug, Clone)]
1243pub struct ConstraintMintFreezeAuthority {
1244    pub mint_freeze_auth: Expr,
1245}
1246
1247#[derive(Debug, Clone)]
1248pub struct ConstraintMintDecimals {
1249    pub decimals: Expr,
1250}
1251
1252#[derive(Debug, Clone)]
1253pub struct ConstraintTokenBump {
1254    pub bump: Option<Expr>,
1255}
1256
1257#[derive(Debug, Clone)]
1258pub struct ConstraintProgramSeed {
1259    pub program_seed: Expr,
1260}
1261
1262#[derive(Debug, Clone)]
1263pub struct ConstraintAssociatedToken {
1264    pub wallet: Expr,
1265    pub mint: Expr,
1266    pub token_program: Option<Expr>,
1267}
1268
1269#[derive(Debug, Clone)]
1270pub struct ConstraintTokenAccountGroup {
1271    pub mint: Option<Expr>,
1272    pub authority: Option<Expr>,
1273    pub token_program: Option<Expr>,
1274}
1275
1276#[derive(Debug, Clone)]
1277pub struct ConstraintTokenMintGroup {
1278    pub decimals: Option<Expr>,
1279    pub mint_authority: Option<Expr>,
1280    pub freeze_authority: Option<Expr>,
1281    pub token_program: Option<Expr>,
1282    pub group_pointer_authority: Option<Expr>,
1283    pub group_pointer_group_address: Option<Expr>,
1284    pub group_member_pointer_authority: Option<Expr>,
1285    pub group_member_pointer_member_address: Option<Expr>,
1286    pub metadata_pointer_authority: Option<Expr>,
1287    pub metadata_pointer_metadata_address: Option<Expr>,
1288    pub close_authority: Option<Expr>,
1289    pub permanent_delegate: Option<Expr>,
1290    pub transfer_hook_authority: Option<Expr>,
1291    pub transfer_hook_program_id: Option<Expr>,
1292    pub pausable_authority: Option<Expr>,
1293}
1294
1295// Syntax context object for preserving metadata about the inner item.
1296#[derive(Debug, Clone)]
1297pub struct Context<T> {
1298    span: Span,
1299    inner: T,
1300}
1301
1302impl<T> Context<T> {
1303    pub fn new(span: Span, inner: T) -> Self {
1304        Self { span, inner }
1305    }
1306
1307    pub fn into_inner(self) -> T {
1308        self.inner
1309    }
1310}
1311
1312impl<T> Deref for Context<T> {
1313    type Target = T;
1314
1315    fn deref(&self) -> &Self::Target {
1316        &self.inner
1317    }
1318}
1319
1320impl<T> Context<T> {
1321    pub fn span(&self) -> Span {
1322        self.span
1323    }
1324}
1325
1326impl<T: ToTokens> ToTokens for Context<T> {
1327    fn to_tokens(&self, tokens: &mut TokenStream) {
1328        self.inner.to_tokens(tokens)
1329    }
1330}