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